Compare commits

..

1 Commits

Author SHA1 Message Date
Svarog
4a25ef90ea [NOD-1064] Don't send GetBlockInvsMsg with lowHash = nil (#769) 2020-06-21 09:09:27 +03:00
665 changed files with 34027 additions and 24294 deletions

1347
addrmgr/addrmanager.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,30 +2,28 @@
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package addressmanager
package addrmgr
import (
"fmt"
"github.com/kaspanet/kaspad/network/domainmessage"
"io/ioutil"
"github.com/kaspanet/kaspad/config"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util/subnetworkid"
"net"
"reflect"
"testing"
"time"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/wire"
)
// naTest is used to describe a test to be performed against the NetAddressKey
// method.
type naTest struct {
in domainmessage.NetAddress
want AddressKey
in wire.NetAddress
want string
}
// naTests houses all of the tests to be performed against the NetAddressKey
@@ -96,57 +94,36 @@ func addNaTests() {
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)
na := *domainmessage.NewNetAddressIPPort(nip, port, domainmessage.SFNodeNetwork)
na := *wire.NewNetAddressIPPort(nip, port, wire.SFNodeNetwork)
test := naTest{na, want}
naTests = append(naTests, test)
}
func lookupFuncForTest(host string) ([]net.IP, error) {
func lookupFunc(host string) ([]net.IP, error) {
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 = New(cfg, databaseContext)
return addressManager, func() {
err := databaseContext.Close()
if err != nil {
t.Fatalf("error closing the database: %s", err)
}
}
}
func TestStartStop(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestStartStop", nil)
defer teardown()
err := amgr.Start()
if err != nil {
t.Fatalf("Address Manager failed to start: %v", err)
}
err = amgr.Stop()
n := New("teststartstop", lookupFunc, nil)
n.Start()
err := n.Stop()
if err != nil {
t.Fatalf("Address Manager failed to stop: %v", err)
}
}
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("")
addrErr := &net.AddrError{}
var tests = []struct {
@@ -171,8 +148,7 @@ func TestAddAddressByIP(t *testing.T) {
},
}
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
defer teardown()
amgr := New("testaddressbyip", nil, nil)
for i, test := range tests {
err := amgr.AddAddressByIP(test.addrIP, nil)
if test.err != nil && err == nil {
@@ -192,44 +168,52 @@ func TestAddAddressByIP(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 {
address domainmessage.NetAddress
address wire.NetAddress
priority AddressPriority
valid bool
}{
{
domainmessage.NetAddress{IP: net.ParseIP("192.168.0.100")},
wire.NetAddress{IP: net.ParseIP("192.168.0.100")},
InterfacePrio,
false,
},
{
domainmessage.NetAddress{IP: net.ParseIP("204.124.1.1")},
wire.NetAddress{IP: net.ParseIP("204.124.1.1")},
InterfacePrio,
true,
},
{
domainmessage.NetAddress{IP: net.ParseIP("204.124.1.1")},
wire.NetAddress{IP: net.ParseIP("204.124.1.1")},
BoundPrio,
true,
},
{
domainmessage.NetAddress{IP: net.ParseIP("::1")},
wire.NetAddress{IP: net.ParseIP("::1")},
InterfacePrio,
false,
},
{
domainmessage.NetAddress{IP: net.ParseIP("fe80::1")},
wire.NetAddress{IP: net.ParseIP("fe80::1")},
InterfacePrio,
false,
},
{
domainmessage.NetAddress{IP: net.ParseIP("2620:100::1")},
wire.NetAddress{IP: net.ParseIP("2620:100::1")},
InterfacePrio,
true,
},
}
amgr, teardown := newAddrManagerForTest(t, "TestAddLocalAddress", nil)
defer teardown()
amgr := New("testaddlocaladdress", nil, nil)
for x, test := range tests {
result := amgr.AddLocalAddress(&test.address, test.priority)
if result == nil && !test.valid {
@@ -246,22 +230,30 @@ func TestAddLocalAddress(t *testing.T) {
}
func TestAttempt(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestAttempt", nil)
defer teardown()
originalActiveCfg := config.ActiveConfig()
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
err := amgr.AddAddressByIP(someIP+":8333", nil)
err := n.AddAddressByIP(someIP+":8333", nil)
if err != nil {
t.Fatalf("Adding address failed: %v", err)
}
ka := amgr.GetAddress()
ka := n.GetAddress()
if !ka.LastAttempt().IsZero() {
t.Errorf("Address should not have attempts, but does")
}
na := ka.NetAddress()
amgr.Attempt(na)
n.Attempt(na)
if ka.LastAttempt().IsZero() {
t.Errorf("Address should have an attempt, but does not")
@@ -269,20 +261,28 @@ func TestAttempt(t *testing.T) {
}
func TestConnected(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestConnected", nil)
defer teardown()
originalActiveCfg := config.ActiveConfig()
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
err := amgr.AddAddressByIP(someIP+":8333", nil)
err := n.AddAddressByIP(someIP+":8333", nil)
if err != nil {
t.Fatalf("Adding address failed: %v", err)
}
ka := amgr.GetAddress()
ka := n.GetAddress()
na := ka.NetAddress()
// 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) {
t.Errorf("Address should have a new timestamp, but does not")
@@ -290,50 +290,66 @@ func TestConnected(t *testing.T) {
}
func TestNeedMoreAddresses(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestNeedMoreAddresses", nil)
defer teardown()
originalActiveCfg := config.ActiveConfig()
config.SetActiveConfig(&config.Config{
Flags: &config.Flags{
NetworkFlags: config.NetworkFlags{
ActiveNetParams: &dagconfig.SimnetParams},
},
})
defer config.SetActiveConfig(originalActiveCfg)
n := New("testneedmoreaddresses", lookupFunc, nil)
addrsToAdd := 1500
b := amgr.NeedMoreAddresses()
b := n.NeedMoreAddresses()
if !b {
t.Errorf("Expected that we need more addresses")
}
addrs := make([]*domainmessage.NetAddress, addrsToAdd)
addrs := make([]*wire.NetAddress, addrsToAdd)
var err error
for i := 0; i < addrsToAdd; i++ {
s := AddressKey(fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60))
addrs[i], err = amgr.DeserializeNetAddress(s)
s := fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60)
addrs[i], err = n.DeserializeNetAddress(s)
if err != nil {
t.Errorf("Failed to turn %s into an address: %v", s, err)
}
}
srcAddr := domainmessage.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)
numAddrs := amgr.TotalNumAddresses()
n.AddAddresses(addrs, srcAddr, nil)
numAddrs := n.TotalNumAddresses()
if numAddrs > addrsToAdd {
t.Errorf("Number of addresses is too many %d vs %d", numAddrs, addrsToAdd)
}
b = amgr.NeedMoreAddresses()
b = n.NeedMoreAddresses()
if b {
t.Errorf("Expected that we don't need more addresses")
}
}
func TestGood(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestGood", nil)
defer teardown()
originalActiveCfg := config.ActiveConfig()
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
addrs := make([]*domainmessage.NetAddress, addrsToAdd)
addrs := make([]*wire.NetAddress, addrsToAdd)
subnetworkCount := 32
subnetworkIDs := make([]*subnetworkid.SubnetworkID, subnetworkCount)
var err error
for i := 0; i < addrsToAdd; i++ {
s := AddressKey(fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60))
addrs[i], err = amgr.DeserializeNetAddress(s)
s := fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60)
addrs[i], err = n.DeserializeNetAddress(s)
if err != nil {
t.Errorf("Failed to turn %s into an address: %v", s, err)
}
@@ -343,26 +359,26 @@ func TestGood(t *testing.T) {
subnetworkIDs[i] = &subnetworkid.SubnetworkID{0xff - byte(i)}
}
srcAddr := domainmessage.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 {
amgr.Good(addr, subnetworkIDs[i%subnetworkCount])
n.Good(addr, subnetworkIDs[i%subnetworkCount])
}
numAddrs := amgr.TotalNumAddresses()
numAddrs := n.TotalNumAddresses()
if 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 {
t.Errorf("Number of addresses in cache: got %d, want positive and less than %d",
numCache, numAddrs/4)
}
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 {
t.Errorf("Number of addresses in subnetwork cache: got %d, want positive and less than %d",
numCache, numAddrs/4/subnetworkCount)
@@ -371,18 +387,26 @@ func TestGood(t *testing.T) {
}
func TestGoodChangeSubnetworkID(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestGoodChangeSubnetworkID", nil)
defer teardown()
addr := domainmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
originalActiveCfg := config.ActiveConfig()
config.SetActiveConfig(&config.Config{
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)
srcAddr := domainmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
oldSubnetwork := subnetworkid.SubnetworkIDNative
amgr.AddAddress(addr, srcAddr, oldSubnetwork)
amgr.Good(addr, oldSubnetwork)
n.AddAddress(addr, srcAddr, oldSubnetwork)
n.Good(addr, oldSubnetwork)
// make sure address was saved to addressIndex under oldSubnetwork
ka := amgr.knownAddress(addr)
// make sure address was saved to addrIndex under oldSubnetwork
ka := n.find(addr)
if ka == nil {
t.Fatalf("Address was not found after first time .Good called")
}
@@ -391,10 +415,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
}
// 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
for _, ka := range bucket {
if NetAddressKey(ka.NetAddress()) == addrKey {
for e := bucket.Front(); e != nil; e = e.Next() {
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
wasFound = true
}
}
@@ -404,10 +428,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
// now call .Good again with a different subnetwork
newSubnetwork := subnetworkid.SubnetworkIDRegistry
amgr.Good(addr, newSubnetwork)
n.Good(addr, newSubnetwork)
// make sure address was updated in addressIndex under newSubnetwork
ka = amgr.knownAddress(addr)
// make sure address was updated in addrIndex under newSubnetwork
ka = n.find(addr)
if ka == nil {
t.Fatalf("Address was not found after second time .Good called")
}
@@ -416,10 +440,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
}
// 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
for _, ka := range bucket {
if NetAddressKey(ka.NetAddress()) == addrKey {
for e := bucket.Front(); e != nil; e = e.Next() {
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
wasFound = true
}
}
@@ -428,10 +452,10 @@ func TestGoodChangeSubnetworkID(t *testing.T) {
}
// 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
for _, ka := range bucket {
if NetAddressKey(ka.NetAddress()) == addrKey {
for e := bucket.Front(); e != nil; e = e.Next() {
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
wasFound = true
}
}
@@ -441,36 +465,44 @@ func TestGoodChangeSubnetworkID(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}
amgr, teardown := newAddrManagerForTest(t, "TestGetAddress", localSubnetworkID)
defer teardown()
n := New("testgetaddress", lookupFunc, localSubnetworkID)
// 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)
}
// Add a new address and get it
err := amgr.AddAddressByIP(someIP+":8332", localSubnetworkID)
err := n.AddAddressByIP(someIP+":8332", localSubnetworkID)
if err != nil {
t.Fatalf("Adding address failed: %v", err)
}
ka := amgr.GetAddress()
ka := n.GetAddress()
if ka == nil {
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.
actualSubnetworkID := &subnetworkid.SubnetworkID{0xfe}
amgr.Good(ka.NetAddress(), actualSubnetworkID)
ka = amgr.GetAddress()
n.Good(ka.NetAddress(), actualSubnetworkID)
ka = n.GetAddress()
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)
}
// 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 {
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
}
@@ -478,11 +510,11 @@ func TestGetAddress(t *testing.T) {
// Now we repeat the same process, but now the address has the expected subnetwork ID.
// Add a new address and get it
err = amgr.AddAddressByIP(someIP+":8333", localSubnetworkID)
err = n.AddAddressByIP(someIP+":8333", localSubnetworkID)
if err != nil {
t.Fatalf("Adding address failed: %v", err)
}
ka = amgr.GetAddress()
ka = n.GetAddress()
if ka == nil {
t.Fatalf("Did not get an address where there is one in the pool")
}
@@ -492,11 +524,11 @@ func TestGetAddress(t *testing.T) {
if !ka.SubnetworkID().IsEqual(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
amgr.Good(ka.NetAddress(), localSubnetworkID)
ka = amgr.GetAddress()
n.Good(ka.NetAddress(), localSubnetworkID)
ka = n.GetAddress()
if ka == nil {
t.Fatalf("Did not get an address where there is one in the pool")
}
@@ -507,14 +539,23 @@ func TestGetAddress(t *testing.T) {
t.Errorf("Wrong Subnetwork ID: got %v, want %v", ka.SubnetworkID(), localSubnetworkID)
}
numAddrs = amgr.TotalNumAddresses()
numAddrs = n.TotalNumAddresses()
if numAddrs != 2 {
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
}
}
func TestGetBestLocalAddress(t *testing.T) {
localAddrs := []domainmessage.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("::1")},
{IP: net.ParseIP("fe80::1")},
@@ -522,49 +563,48 @@ func TestGetBestLocalAddress(t *testing.T) {
}
var tests = []struct {
remoteAddr domainmessage.NetAddress
want0 domainmessage.NetAddress
want1 domainmessage.NetAddress
want2 domainmessage.NetAddress
want3 domainmessage.NetAddress
remoteAddr wire.NetAddress
want0 wire.NetAddress
want1 wire.NetAddress
want2 wire.NetAddress
want3 wire.NetAddress
}{
{
// Remote connection from public IPv4
domainmessage.NetAddress{IP: net.ParseIP("204.124.8.1")},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.ParseIP("204.124.8.100")},
domainmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
wire.NetAddress{IP: net.ParseIP("204.124.8.1")},
wire.NetAddress{IP: net.IPv4zero},
wire.NetAddress{IP: net.IPv4zero},
wire.NetAddress{IP: net.ParseIP("204.124.8.100")},
wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
},
{
// Remote connection from private IPv4
domainmessage.NetAddress{IP: net.ParseIP("172.16.0.254")},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.IPv4zero},
wire.NetAddress{IP: net.ParseIP("172.16.0.254")},
wire.NetAddress{IP: net.IPv4zero},
wire.NetAddress{IP: net.IPv4zero},
wire.NetAddress{IP: net.IPv4zero},
wire.NetAddress{IP: net.IPv4zero},
},
{
// Remote connection from public IPv6
domainmessage.NetAddress{IP: net.ParseIP("2602:100:abcd::102")},
domainmessage.NetAddress{IP: net.IPv6zero},
domainmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
domainmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
domainmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
wire.NetAddress{IP: net.ParseIP("2602:100:abcd::102")},
wire.NetAddress{IP: net.IPv6zero},
wire.NetAddress{IP: net.ParseIP("2001:470::1")},
wire.NetAddress{IP: net.ParseIP("2001:470::1")},
wire.NetAddress{IP: net.ParseIP("2001:470::1")},
},
/* XXX
{
// Remote connection from Tor
domainmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43::100")},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.ParseIP("204.124.8.100")},
domainmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
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)
defer teardown()
amgr := New("testgetbestlocaladdress", nil, nil)
// Test against default when there's no address
for x, test := range tests {
@@ -591,7 +631,7 @@ func TestGetBestLocalAddress(t *testing.T) {
}
// Add a public IP to the list of local addresses.
localAddr := domainmessage.NetAddress{IP: net.ParseIP("204.124.8.100")}
localAddr := wire.NetAddress{IP: net.ParseIP("204.124.8.100")}
amgr.AddLocalAddress(&localAddr, InterfacePrio)
// Test against want2
@@ -605,7 +645,7 @@ func TestGetBestLocalAddress(t *testing.T) {
}
/*
// Add a Tor generated IP address
localAddr = domainmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}
localAddr = wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}
amgr.AddLocalAddress(&localAddr, ManualPrio)
// Test against want3
for x, test := range tests {

View File

@@ -1,5 +1,5 @@
/*
Package addressmanager implements concurrency safe Kaspa address manager.
Package addrmgr implements concurrency safe Kaspa address manager.
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
providing usable addresses.
*/
package addressmanager
package addrmgr

25
addrmgr/internal_test.go Normal file
View 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}
}

View File

@@ -2,35 +2,33 @@
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package addressmanager
package addrmgr
import (
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/util/mstime"
"time"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
)
// KnownAddress tracks information about a known network address that is used
// to determine how viable an address is.
type KnownAddress struct {
netAddress *domainmessage.NetAddress
sourceAddress *domainmessage.NetAddress
attempts int
lastAttempt mstime.Time
lastSuccess mstime.Time
tried bool
referenceCount int // reference count of new buckets
subnetworkID *subnetworkid.SubnetworkID
isBanned bool
bannedTime mstime.Time
na *wire.NetAddress
srcAddr *wire.NetAddress
attempts int
lastattempt time.Time
lastsuccess time.Time
tried bool
refs int // reference count of new buckets
subnetworkID *subnetworkid.SubnetworkID
}
// NetAddress returns the underlying domainmessage.NetAddress associated with the
// NetAddress returns the underlying wire.NetAddress associated with the
// known address.
func (ka *KnownAddress) NetAddress() *domainmessage.NetAddress {
return ka.netAddress
func (ka *KnownAddress) NetAddress() *wire.NetAddress {
return ka.na
}
// 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.
func (ka *KnownAddress) LastAttempt() mstime.Time {
return ka.lastAttempt
func (ka *KnownAddress) LastAttempt() time.Time {
return ka.lastattempt
}
// 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
// attempted and how often attempts to connect to it have failed.
func (ka *KnownAddress) chance() float64 {
now := mstime.Now()
lastAttempt := now.Sub(ka.lastAttempt)
now := time.Now()
lastAttempt := now.Sub(ka.lastattempt)
if 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
// worth keeping hold of.
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
}
// 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
}
// 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
}
// Never succeeded?
if ka.lastSuccess.IsZero() && ka.attempts >= numRetries {
if ka.lastsuccess.IsZero() && ka.attempts >= numRetries {
return true
}
// 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 {
return true
}

View 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.")
}
}

View File

@@ -2,10 +2,10 @@
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package addressmanager
package addrmgr
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/logger"
"github.com/kaspanet/kaspad/util/panics"
)

View File

@@ -2,11 +2,14 @@
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package addressmanager
package addrmgr
import (
"github.com/kaspanet/kaspad/network/domainmessage"
"net"
"github.com/kaspanet/kaspad/config"
"github.com/kaspanet/kaspad/wire"
)
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.
func IsIPv4(na *domainmessage.NetAddress) bool {
func IsIPv4(na *wire.NetAddress) bool {
return na.IP.To4() != nil
}
// IsLocal returns whether or not the given address is a local address.
func IsLocal(na *domainmessage.NetAddress) bool {
func IsLocal(na *wire.NetAddress) bool {
return na.IP.IsLoopback() || zero4Net.Contains(na.IP)
}
// 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,
// 172.16.0.0/12, or 192.168.0.0/16).
func IsRFC1918(na *domainmessage.NetAddress) bool {
func IsRFC1918(na *wire.NetAddress) bool {
for _, rfc := range rfc1918Nets {
if rfc.Contains(na.IP) {
return true
@@ -108,56 +111,56 @@ func IsRFC1918(na *domainmessage.NetAddress) bool {
// IsRFC2544 returns whether or not the passed address is part of the IPv4
// address space as defined by RFC2544 (198.18.0.0/15)
func IsRFC2544(na *domainmessage.NetAddress) bool {
func IsRFC2544(na *wire.NetAddress) bool {
return rfc2544Net.Contains(na.IP)
}
// IsRFC3849 returns whether or not the passed address is part of the IPv6
// documentation range as defined by RFC3849 (2001:DB8::/32).
func IsRFC3849(na *domainmessage.NetAddress) bool {
func IsRFC3849(na *wire.NetAddress) bool {
return rfc3849Net.Contains(na.IP)
}
// IsRFC3927 returns whether or not the passed address is part of the IPv4
// autoconfiguration range as defined by RFC3927 (169.254.0.0/16).
func IsRFC3927(na *domainmessage.NetAddress) bool {
func IsRFC3927(na *wire.NetAddress) bool {
return rfc3927Net.Contains(na.IP)
}
// IsRFC3964 returns whether or not the passed address is part of the IPv6 to
// IPv4 encapsulation range as defined by RFC3964 (2002::/16).
func IsRFC3964(na *domainmessage.NetAddress) bool {
func IsRFC3964(na *wire.NetAddress) bool {
return rfc3964Net.Contains(na.IP)
}
// IsRFC4193 returns whether or not the passed address is part of the IPv6
// unique local range as defined by RFC4193 (FC00::/7).
func IsRFC4193(na *domainmessage.NetAddress) bool {
func IsRFC4193(na *wire.NetAddress) bool {
return rfc4193Net.Contains(na.IP)
}
// IsRFC4380 returns whether or not the passed address is part of the IPv6
// teredo tunneling over UDP range as defined by RFC4380 (2001::/32).
func IsRFC4380(na *domainmessage.NetAddress) bool {
func IsRFC4380(na *wire.NetAddress) bool {
return rfc4380Net.Contains(na.IP)
}
// IsRFC4843 returns whether or not the passed address is part of the IPv6
// ORCHID range as defined by RFC4843 (2001:10::/28).
func IsRFC4843(na *domainmessage.NetAddress) bool {
func IsRFC4843(na *wire.NetAddress) bool {
return rfc4843Net.Contains(na.IP)
}
// IsRFC4862 returns whether or not the passed address is part of the IPv6
// stateless address autoconfiguration range as defined by RFC4862 (FE80::/64).
func IsRFC4862(na *domainmessage.NetAddress) bool {
func IsRFC4862(na *wire.NetAddress) bool {
return rfc4862Net.Contains(na.IP)
}
// 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,
// 198.51.100.0/24, 203.0.113.0/24)
func IsRFC5737(na *domainmessage.NetAddress) bool {
func IsRFC5737(na *wire.NetAddress) bool {
for _, rfc := range rfc5737Net {
if rfc.Contains(na.IP) {
return true
@@ -169,19 +172,19 @@ func IsRFC5737(na *domainmessage.NetAddress) bool {
// IsRFC6052 returns whether or not the passed address is part of the IPv6
// well-known prefix range as defined by RFC6052 (64:FF9B::/96).
func IsRFC6052(na *domainmessage.NetAddress) bool {
func IsRFC6052(na *wire.NetAddress) bool {
return rfc6052Net.Contains(na.IP)
}
// 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).
func IsRFC6145(na *domainmessage.NetAddress) bool {
func IsRFC6145(na *wire.NetAddress) bool {
return rfc6145Net.Contains(na.IP)
}
// IsRFC6598 returns whether or not the passed address is part of the IPv4
// shared address space specified by RFC6598 (100.64.0.0/10)
func IsRFC6598(na *domainmessage.NetAddress) bool {
func IsRFC6598(na *wire.NetAddress) bool {
return rfc6598Net.Contains(na.IP)
}
@@ -189,7 +192,7 @@ func IsRFC6598(na *domainmessage.NetAddress) bool {
// considered invalid under the following circumstances:
// IPv4: It is either a zero or all bits set address.
// IPv6: It is either a zero or RFC3849 documentation address.
func IsValid(na *domainmessage.NetAddress) bool {
func IsValid(na *wire.NetAddress) bool {
// IsUnspecified returns if address is 0, so only all bits set, and
// RFC3849 need to be explicitly checked.
return na.IP != nil && !(na.IP.IsUnspecified() ||
@@ -199,8 +202,8 @@ func IsValid(na *domainmessage.NetAddress) bool {
// 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
// in any reserved ranges.
func (am *AddressManager) IsRoutable(na *domainmessage.NetAddress) bool {
if am.cfg.NetParams().AcceptUnroutable {
func IsRoutable(na *wire.NetAddress) bool {
if config.ActiveConfig().NetParams().AcceptUnroutable {
return !IsLocal(na)
}
@@ -214,11 +217,11 @@ func (am *AddressManager) IsRoutable(na *domainmessage.NetAddress) bool {
// 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
// address.
func (am *AddressManager) GroupKey(na *domainmessage.NetAddress) string {
func GroupKey(na *wire.NetAddress) string {
if IsLocal(na) {
return "local"
}
if !am.IsRoutable(na) {
if !IsRoutable(na) {
return "unroutable"
}
if IsIPv4(na) {

View File

@@ -2,21 +2,32 @@
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package addressmanager
package addrmgr_test
import (
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/config"
"github.com/kaspanet/kaspad/dagconfig"
"net"
"testing"
"github.com/kaspanet/kaspad/addrmgr"
"github.com/kaspanet/kaspad/wire"
)
// TestIPTypes ensures the various functions which determine the type of an IP
// address based on RFCs work as intended.
func TestIPTypes(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
defer teardown()
originalActiveCfg := config.ActiveConfig()
config.SetActiveConfig(&config.Config{
Flags: &config.Flags{
NetworkFlags: config.NetworkFlags{
ActiveNetParams: &dagconfig.SimnetParams},
},
})
defer config.SetActiveConfig(originalActiveCfg)
type ipTest struct {
in domainmessage.NetAddress
in wire.NetAddress
rfc1918 bool
rfc2544 bool
rfc3849 bool
@@ -39,7 +50,7 @@ func TestIPTypes(t *testing.T) {
rfc4193, rfc4380, rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598,
local, valid, routable bool) ipTest {
nip := net.ParseIP(ip)
na := *domainmessage.NewNetAddressIPPort(nip, 16111, domainmessage.SFNodeNetwork)
na := *wire.NewNetAddressIPPort(nip, 16111, wire.SFNodeNetwork)
test := ipTest{na, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380,
rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, local, valid, routable}
return test
@@ -88,55 +99,55 @@ func TestIPTypes(t *testing.T) {
t.Logf("Running %d tests", len(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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
}
@@ -145,8 +156,14 @@ func TestIPTypes(t *testing.T) {
// TestGroupKey tests the GroupKey function to ensure it properly groups various
// IP addresses.
func TestGroupKey(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
defer teardown()
originalActiveCfg := config.ActiveConfig()
config.SetActiveConfig(&config.Config{
Flags: &config.Flags{
NetworkFlags: config.NetworkFlags{
ActiveNetParams: &dagconfig.SimnetParams},
},
})
defer config.SetActiveConfig(originalActiveCfg)
tests := []struct {
name string
@@ -195,8 +212,8 @@ func TestGroupKey(t *testing.T) {
for i, test := range tests {
nip := net.ParseIP(test.ip)
na := *domainmessage.NewNetAddressIPPort(nip, 8333, domainmessage.SFNodeNetwork)
if key := amgr.GroupKey(&na); key != test.expected {
na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork)
if key := addrmgr.GroupKey(&na); key != test.expected {
t.Errorf("TestGroupKey #%d (%s): unexpected group key "+
"- got '%s', want '%s'", i, test.name,
key, test.expected)

View File

@@ -1,247 +0,0 @@
package app
import (
"fmt"
"sync/atomic"
"github.com/kaspanet/kaspad/network/addressmanager"
"github.com/kaspanet/kaspad/network/netadapter/id"
"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/dbaccess"
"github.com/kaspanet/kaspad/infrastructure/signal"
"github.com/kaspanet/kaspad/network/connmanager"
"github.com/kaspanet/kaspad/network/dnsseed"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/network/netadapter"
"github.com/kaspanet/kaspad/network/protocol"
"github.com/kaspanet/kaspad/network/rpc"
"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 := addressmanager.New(cfg, databaseContext)
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, domainmessage.SFNodeNetwork, false, nil,
a.cfg.Lookup, func(addresses []*domainmessage.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
}

View File

@@ -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)

155
blockdag/accept.go Normal file
View 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
View 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
}

View File

@@ -3,7 +3,7 @@ package blockdag
import (
"testing"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util/daghash"
)
@@ -11,14 +11,14 @@ import (
func TestBlockHeap(t *testing.T) {
// Create a new database and DAG instance to run tests against.
dag, teardownFunc, err := DAGSetup("TestBlockHeap", true, Config{
DAGParams: &dagconfig.SimnetParams,
DAGParams: &dagconfig.MainnetParams,
})
if err != nil {
t.Fatalf("TestBlockHeap: Failed to setup DAG instance: %s", err)
}
defer teardownFunc()
block0Header := dagconfig.SimnetParams.GenesisBlock.Header
block0Header := dagconfig.MainnetParams.GenesisBlock.Header
block0, _ := dag.newBlockNode(&block0Header, newBlockSet())
block100000Header := Block100000.Header

View File

@@ -5,12 +5,10 @@
package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/dbaccess"
"sync"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"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.
//
// 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()
defer bi.RUnlock()
node, ok := bi.index[*hash]
return node, ok
node := bi.index[*hash]
return node
}
// 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() {
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
}

View File

@@ -1,9 +1,10 @@
package blockdag
import (
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util/mstime"
"testing"
"time"
"github.com/kaspanet/kaspad/dagconfig"
)
func TestAncestorErrors(t *testing.T) {
@@ -17,7 +18,7 @@ func TestAncestorErrors(t *testing.T) {
}
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
ancestor := node.SelectedAncestor(3)
if ancestor != nil {

102
blockdag/blocklocator.go Normal file
View 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
}

View File

@@ -6,14 +6,13 @@ package blockdag
import (
"fmt"
"math"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/pkg/errors"
"math"
"time"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
)
// 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).
// selectedParentAnticone is used to update reachability data we store for future reachability queries.
// This function is NOT safe for concurrent access.
func (dag *BlockDAG) newBlockNode(blockHeader *domainmessage.BlockHeader, parents blockSet) (node *blockNode, selectedParentAnticone []*blockNode) {
func (dag *BlockDAG) newBlockNode(blockHeader *wire.BlockHeader, parents blockSet) (node *blockNode, selectedParentAnticone []*blockNode) {
node = &blockNode{
parents: parents,
children: make(blockSet),
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),
}
@@ -121,7 +120,7 @@ func (dag *BlockDAG) newBlockNode(blockHeader *domainmessage.BlockHeader, parent
node.version = blockHeader.Version
node.bits = blockHeader.Bits
node.nonce = blockHeader.Nonce
node.timestamp = blockHeader.Timestamp.UnixMilliseconds()
node.timestamp = blockHeader.Timestamp.Unix()
node.hashMerkleRoot = blockHeader.HashMerkleRoot
node.acceptedIDMerkleRoot = blockHeader.AcceptedIDMerkleRoot
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.
//
// This function is safe for concurrent access.
func (node *blockNode) Header() *domainmessage.BlockHeader {
func (node *blockNode) Header() *wire.BlockHeader {
// No lock is needed because all accessed fields are immutable.
return &domainmessage.BlockHeader{
return &wire.BlockHeader{
Version: node.version,
ParentHashes: node.ParentHashes(),
HashMerkleRoot: node.hashMerkleRoot,
AcceptedIDMerkleRoot: node.acceptedIDMerkleRoot,
UTXOCommitment: node.utxoCommitment,
Timestamp: node.time(),
Timestamp: time.Unix(node.timestamp, 0),
Bits: node.bits,
Nonce: node.nonce,
}
@@ -205,13 +204,13 @@ func (node *blockNode) RelativeAncestor(distance uint64) *blockNode {
// prior to, and including, the block node.
//
// 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)
medianTimestamp, err := window.medianTimestamp()
if err != nil {
panic(fmt.Sprintf("blueBlockWindow: %s", err))
}
return mstime.UnixMilliseconds(medianTimestamp)
return time.Unix(medianTimestamp, 0)
}
func (node *blockNode) ParentHashes() []*daghash.Hash {
@@ -224,14 +223,10 @@ func (node *blockNode) isGenesis() bool {
}
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.
func (node blockNode) String() string {
return node.hash.String()
}
func (node *blockNode) time() mstime.Time {
return mstime.UnixMilliseconds(node.timestamp)
}

View File

@@ -1,7 +1,7 @@
package blockdag
import (
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util/daghash"
"testing"
)

View File

@@ -1,7 +1,7 @@
package blockdag
import (
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"reflect"
@@ -53,12 +53,12 @@ func TestBlueBlockWindow(t *testing.T) {
{
parents: []string{"C", "D"},
id: "E",
expectedWindowWithGenesisPadding: []string{"C", "D", "B", "A", "A", "A", "A", "A", "A", "A"},
expectedWindowWithGenesisPadding: []string{"D", "C", "B", "A", "A", "A", "A", "A", "A", "A"},
},
{
parents: []string{"C", "D"},
id: "F",
expectedWindowWithGenesisPadding: []string{"C", "D", "B", "A", "A", "A", "A", "A", "A", "A"},
expectedWindowWithGenesisPadding: []string{"D", "C", "B", "A", "A", "A", "A", "A", "A", "A"},
},
{
parents: []string{"A"},
@@ -73,37 +73,37 @@ func TestBlueBlockWindow(t *testing.T) {
{
parents: []string{"H", "F"},
id: "I",
expectedWindowWithGenesisPadding: []string{"F", "C", "D", "B", "A", "A", "A", "A", "A", "A"},
expectedWindowWithGenesisPadding: []string{"F", "D", "C", "B", "A", "A", "A", "A", "A", "A"},
},
{
parents: []string{"I"},
id: "J",
expectedWindowWithGenesisPadding: []string{"I", "F", "C", "D", "B", "A", "A", "A", "A", "A"},
expectedWindowWithGenesisPadding: []string{"I", "F", "D", "C", "B", "A", "A", "A", "A", "A"},
},
{
parents: []string{"J"},
id: "K",
expectedWindowWithGenesisPadding: []string{"J", "I", "F", "C", "D", "B", "A", "A", "A", "A"},
expectedWindowWithGenesisPadding: []string{"J", "I", "F", "D", "C", "B", "A", "A", "A", "A"},
},
{
parents: []string{"K"},
id: "L",
expectedWindowWithGenesisPadding: []string{"K", "J", "I", "F", "C", "D", "B", "A", "A", "A"},
expectedWindowWithGenesisPadding: []string{"K", "J", "I", "F", "D", "C", "B", "A", "A", "A"},
},
{
parents: []string{"L"},
id: "M",
expectedWindowWithGenesisPadding: []string{"L", "K", "J", "I", "F", "C", "D", "B", "A", "A"},
expectedWindowWithGenesisPadding: []string{"L", "K", "J", "I", "F", "D", "C", "B", "A", "A"},
},
{
parents: []string{"M"},
id: "N",
expectedWindowWithGenesisPadding: []string{"M", "L", "K", "J", "I", "F", "C", "D", "B", "A"},
expectedWindowWithGenesisPadding: []string{"M", "L", "K", "J", "I", "F", "D", "C", "B", "A"},
},
{
parents: []string{"N"},
id: "O",
expectedWindowWithGenesisPadding: []string{"N", "M", "L", "K", "J", "I", "F", "C", "D", "B"},
expectedWindowWithGenesisPadding: []string{"N", "M", "L", "K", "J", "I", "F", "D", "C", "B"},
},
}
@@ -133,10 +133,7 @@ func TestBlueBlockWindow(t *testing.T) {
t.Fatalf("block %v was unexpectedly orphan", blockData.id)
}
node, ok := dag.index.LookupNode(utilBlock.Hash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", utilBlock.Hash())
}
node := dag.index.LookupNode(utilBlock.Hash())
blockByIDMap[blockData.id] = node
idByBlockMap[node] = blockData.id

View File

@@ -4,16 +4,16 @@ import (
"bufio"
"bytes"
"encoding/binary"
"io"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/coinbasepayload"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/util/txsort"
"github.com/pkg/errors"
"io"
"math"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/txsort"
"github.com/kaspanet/kaspad/wire"
)
// compactFeeData is a specialized data type to store a compact list of fees
@@ -76,11 +76,11 @@ func (cfr *compactFeeIterator) next() (uint64, error) {
// getBluesFeeData returns the compactFeeData for all nodes's blues,
// used to calculate the fees this blockNode needs to pay
func (dag *BlockDAG) getBluesFeeData(node *blockNode) (map[daghash.Hash]compactFeeData, error) {
func (node *blockNode) getBluesFeeData(dag *BlockDAG) (map[daghash.Hash]compactFeeData, error) {
bluesFeeData := make(map[daghash.Hash]compactFeeData)
for _, blueBlock := range node.blues {
feeData, err := dbaccess.FetchFeeData(dag.databaseContext, blueBlock.hash)
feeData, err := dbaccess.FetchFeeData(dbaccess.NoTx(), blueBlock.hash)
if err != nil {
return nil, err
}
@@ -98,10 +98,7 @@ func (node *blockNode) validateCoinbaseTransaction(dag *BlockDAG, block *util.Bl
return nil
}
blockCoinbaseTx := block.CoinbaseTransaction().MsgTx()
_, scriptPubKey, extraData, err := coinbasepayload.DeserializeCoinbasePayload(blockCoinbaseTx)
if errors.Is(err, coinbasepayload.ErrIncorrectScriptPubKeyLen) {
return ruleError(ErrBadCoinbaseTransaction, err.Error())
}
scriptPubKey, extraData, err := DeserializeCoinbasePayload(blockCoinbaseTx)
if err != nil {
return err
}
@@ -119,102 +116,132 @@ func (node *blockNode) validateCoinbaseTransaction(dag *BlockDAG, block *util.Bl
// expectedCoinbaseTransaction returns the coinbase transaction for the current block
func (node *blockNode) expectedCoinbaseTransaction(dag *BlockDAG, txsAcceptanceData MultiBlockTxsAcceptanceData, scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
bluesFeeData, err := dag.getBluesFeeData(node)
bluesFeeData, err := node.getBluesFeeData(dag)
if err != nil {
return nil, err
}
txIns := []*domainmessage.TxIn{}
txOuts := []*domainmessage.TxOut{}
txIns := []*wire.TxIn{}
txOuts := []*wire.TxOut{}
for _, blue := range node.blues {
txOut, err := coinbaseOutputForBlueBlock(dag, blue, txsAcceptanceData, bluesFeeData)
txIn, txOut, err := coinbaseInputAndOutputForBlueBlock(dag, blue, txsAcceptanceData, bluesFeeData)
if err != nil {
return nil, err
}
txIns = append(txIns, txIn)
if txOut != nil {
txOuts = append(txOuts, txOut)
}
}
payload, err := coinbasepayload.SerializeCoinbasePayload(node.blueScore, scriptPubKey, extraData)
payload, err := SerializeCoinbasePayload(scriptPubKey, extraData)
if err != nil {
return nil, err
}
coinbaseTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, txIns, txOuts, subnetworkid.SubnetworkIDCoinbase, 0, payload)
coinbaseTx := wire.NewSubnetworkMsgTx(wire.TxVersion, txIns, txOuts, subnetworkid.SubnetworkIDCoinbase, 0, payload)
sortedCoinbaseTx := txsort.Sort(coinbaseTx)
return util.NewTx(sortedCoinbaseTx), nil
}
// coinbaseOutputForBlueBlock calculates the output that should go into the coinbase transaction of blueBlock
// If blueBlock gets no fee - returns nil for txOut
func coinbaseOutputForBlueBlock(dag *BlockDAG, blueBlock *blockNode,
txsAcceptanceData MultiBlockTxsAcceptanceData, feeData map[daghash.Hash]compactFeeData) (*domainmessage.TxOut, error) {
// SerializeCoinbasePayload builds the coinbase payload based on the provided scriptPubKey and extra data.
func SerializeCoinbasePayload(scriptPubKey []byte, extraData []byte) ([]byte, error) {
w := &bytes.Buffer{}
err := wire.WriteVarInt(w, uint64(len(scriptPubKey)))
if err != nil {
return nil, err
}
_, err = w.Write(scriptPubKey)
if err != nil {
return nil, err
}
_, err = w.Write(extraData)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}
// DeserializeCoinbasePayload deserialize the coinbase payload to its component (scriptPubKey and extra data).
func DeserializeCoinbasePayload(tx *wire.MsgTx) (scriptPubKey []byte, extraData []byte, err error) {
r := bytes.NewReader(tx.Payload)
scriptPubKeyLen, err := wire.ReadVarInt(r)
if err != nil {
return nil, nil, err
}
scriptPubKey = make([]byte, scriptPubKeyLen)
_, err = r.Read(scriptPubKey)
if err != nil {
return nil, nil, err
}
extraData = make([]byte, r.Len())
if r.Len() != 0 {
_, err = r.Read(extraData)
if err != nil {
return nil, nil, err
}
}
return scriptPubKey, extraData, nil
}
// feeInputAndOutputForBlueBlock calculates the input and output that should go into the coinbase transaction of blueBlock
// If blueBlock gets no fee - returns only txIn and nil for txOut
func coinbaseInputAndOutputForBlueBlock(dag *BlockDAG, blueBlock *blockNode,
txsAcceptanceData MultiBlockTxsAcceptanceData, feeData map[daghash.Hash]compactFeeData) (
*wire.TxIn, *wire.TxOut, error) {
blockTxsAcceptanceData, ok := txsAcceptanceData.FindAcceptanceData(blueBlock.hash)
if !ok {
return nil, errors.Errorf("No txsAcceptanceData for block %s", blueBlock.hash)
return nil, nil, errors.Errorf("No txsAcceptanceData for block %s", blueBlock.hash)
}
blockFeeData, ok := feeData[*blueBlock.hash]
if !ok {
return nil, errors.Errorf("No feeData for block %s", blueBlock.hash)
return nil, nil, errors.Errorf("No feeData for block %s", blueBlock.hash)
}
if len(blockTxsAcceptanceData.TxAcceptanceData) != blockFeeData.Len() {
return nil, errors.Errorf(
return nil, nil, errors.Errorf(
"length of accepted transaction data(%d) and fee data(%d) is not equal for block %s",
len(blockTxsAcceptanceData.TxAcceptanceData), blockFeeData.Len(), blueBlock.hash)
}
txIn := &wire.TxIn{
SignatureScript: []byte{},
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID(*blueBlock.hash),
Index: math.MaxUint32,
},
Sequence: wire.MaxTxInSequenceNum,
}
totalFees := uint64(0)
feeIterator := blockFeeData.iterator()
for _, txAcceptanceData := range blockTxsAcceptanceData.TxAcceptanceData {
fee, err := feeIterator.next()
if err != nil {
return nil, errors.Errorf("Error retrieving fee from compactFeeData iterator: %s", err)
return nil, nil, errors.Errorf("Error retrieving fee from compactFeeData iterator: %s", err)
}
if txAcceptanceData.IsAccepted {
totalFees += fee
}
}
totalReward := CalcBlockSubsidy(blueBlock.blueScore, dag.Params) + totalFees
totalReward := CalcBlockSubsidy(blueBlock.blueScore, dag.dagParams) + totalFees
if totalReward == 0 {
return nil, nil
return txIn, nil, nil
}
// the ScriptPubKey for the coinbase is parsed from the coinbase payload
_, scriptPubKey, _, err := coinbasepayload.DeserializeCoinbasePayload(blockTxsAcceptanceData.TxAcceptanceData[0].Tx.MsgTx())
scriptPubKey, _, err := DeserializeCoinbasePayload(blockTxsAcceptanceData.TxAcceptanceData[0].Tx.MsgTx())
if err != nil {
return nil, err
return nil, nil, err
}
txOut := &domainmessage.TxOut{
txOut := &wire.TxOut{
Value: totalReward,
ScriptPubKey: scriptPubKey,
}
return txOut, nil
}
// NextBlockCoinbaseTransaction prepares the coinbase transaction for the next mined block
//
// This function CAN'T be called with the DAG lock held.
func (dag *BlockDAG) NextBlockCoinbaseTransaction(scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
return dag.NextBlockCoinbaseTransactionNoLock(scriptPubKey, extraData)
}
// NextBlockCoinbaseTransactionNoLock prepares the coinbase transaction for the next mined block
//
// This function MUST be called with the DAG read-lock held
func (dag *BlockDAG) NextBlockCoinbaseTransactionNoLock(scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
txsAcceptanceData, err := dag.TxsAcceptedByVirtual()
if err != nil {
return nil, err
}
return dag.virtual.blockNode.expectedCoinbaseTransaction(dag, txsAcceptanceData, scriptPubKey, extraData)
return txIn, txOut, nil
}

View File

@@ -12,15 +12,14 @@ import (
"path/filepath"
"strings"
"testing"
"github.com/kaspanet/kaspad/util/mstime"
"time"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
)
// loadUTXOSet returns a utxo view loaded from a file.
@@ -78,7 +77,7 @@ func loadUTXOSet(filename string) (UTXOSet, error) {
if err != nil {
return nil, err
}
utxoSet.utxoCollection[domainmessage.Outpoint{TxID: txID, Index: index}] = entry
utxoSet.utxoCollection[wire.Outpoint{TxID: txID, Index: index}] = entry
}
return utxoSet, nil
@@ -87,7 +86,7 @@ func loadUTXOSet(filename string) (UTXOSet, error) {
// TestSetCoinbaseMaturity makes the ability to set the coinbase maturity
// available when running tests.
func (dag *BlockDAG) TestSetCoinbaseMaturity(maturity uint64) {
dag.Params.BlockCoinbaseMaturity = maturity
dag.dagParams.BlockCoinbaseMaturity = maturity
}
// newTestDAG returns a DAG that is usable for syntetic tests. It is
@@ -96,13 +95,17 @@ func (dag *BlockDAG) TestSetCoinbaseMaturity(maturity uint64) {
// use of it.
func newTestDAG(params *dagconfig.Params) *BlockDAG {
index := newBlockIndex(params)
targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second)
dag := &BlockDAG{
Params: params,
dagParams: params,
timeSource: NewTimeSource(),
targetTimePerBlock: targetTimePerBlock,
difficultyAdjustmentWindowSize: params.DifficultyAdjustmentWindowSize,
TimestampDeviationTolerance: params.TimestampDeviationTolerance,
powMaxBits: util.BigToCompact(params.PowMax),
index: index,
warningCaches: newThresholdCaches(vbNumBits),
deploymentCaches: newThresholdCaches(dagconfig.DefinedDeployments),
}
// Create a genesis block node and block index index populated with it
@@ -116,9 +119,9 @@ func newTestDAG(params *dagconfig.Params) *BlockDAG {
// newTestNode creates a block node connected to the passed parent with the
// provided fields populated and fake values for the other fields.
func newTestNode(dag *BlockDAG, parents blockSet, blockVersion int32, bits uint32, timestamp mstime.Time) *blockNode {
func newTestNode(dag *BlockDAG, parents blockSet, blockVersion int32, bits uint32, timestamp time.Time) *blockNode {
// Make up a header and create a block node from it.
header := &domainmessage.BlockHeader{
header := &wire.BlockHeader{
Version: blockVersion,
ParentHashes: parents.hashes(),
Bits: bits,
@@ -166,7 +169,7 @@ func checkRuleError(gotErr, wantErr error) error {
return nil
}
func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parents ...*domainmessage.MsgBlock) *domainmessage.MsgBlock {
func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parents ...*wire.MsgBlock) *wire.MsgBlock {
parentHashes := make([]*daghash.Hash, len(parents))
for i, parent := range parents {
parentHashes[i] = parent.BlockHash()
@@ -174,22 +177,22 @@ func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parent
return PrepareAndProcessBlockForTest(t, dag, parentHashes, nil)
}
func nodeByMsgBlock(t *testing.T, dag *BlockDAG, block *domainmessage.MsgBlock) *blockNode {
node, ok := dag.index.LookupNode(block.BlockHash())
if !ok {
func nodeByMsgBlock(t *testing.T, dag *BlockDAG, block *wire.MsgBlock) *blockNode {
node := dag.index.LookupNode(block.BlockHash())
if node == nil {
t.Fatalf("couldn't find block node with hash %s", block.BlockHash())
}
return node
}
type fakeTimeSource struct {
time mstime.Time
time time.Time
}
func (fts *fakeTimeSource) Now() mstime.Time {
return fts.time
func (fts *fakeTimeSource) Now() time.Time {
return time.Unix(fts.time.Unix(), 0)
}
func newFakeTimeSource(fakeTime mstime.Time) TimeSource {
func newFakeTimeSource(fakeTime time.Time) TimeSource {
return &fakeTimeSource{time: fakeTime}
}

2103
blockdag/dag.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,9 @@ package blockdag
import (
"fmt"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/pkg/errors"
"math"
"os"
"path/filepath"
@@ -13,16 +16,12 @@ import (
"testing"
"time"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
)
func TestBlockCount(t *testing.T) {
@@ -208,10 +207,10 @@ func TestIsKnownBlock(t *testing.T) {
{hash: dagconfig.SimnetParams.GenesisHash.String(), want: true},
// Block 3b should be present (as a second child of Block 2).
{hash: "46314ca17e117b31b467fe1b26fd36c98ee83e750aa5e3b3c1c32870afbe5984", want: true},
{hash: "48a752afbe36ad66357f751f8dee4f75665d24e18f644d83a3409b398405b46b", want: true},
// Block 100000 should be present (as an orphan).
{hash: "732c891529619d43b5aeb3df42ba25dea483a8c0aded1cf585751ebabea28f29", want: true},
{hash: "65b20b048a074793ebfd1196e49341c8d194dabfc6b44a4fd0c607406e122baf", want: true},
// Random hashes should not be available.
{hash: "123", want: false},
@@ -255,7 +254,7 @@ func TestCalcSequenceLock(t *testing.T) {
// Create a utxo view with a fake utxo for the inputs used in the
// transactions created below. This utxo is added such that it has an
// age of 4 blocks.
msgTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, nil, []*domainmessage.TxOut{{ScriptPubKey: nil, Value: 10}})
msgTx := wire.NewNativeMsgTx(wire.TxVersion, nil, []*wire.TxOut{{ScriptPubKey: nil, Value: 10}})
targetTx := util.NewTx(msgTx)
utxoSet := NewFullUTXOSet()
blueScore := uint64(numBlocksToGenerate) - 4
@@ -270,7 +269,7 @@ func TestCalcSequenceLock(t *testing.T) {
// that the sequence lock heights are always calculated from the same
// point of view that they were originally calculated from for a given
// utxo. That is to say, the height prior to it.
utxo := domainmessage.Outpoint{
utxo := wire.Outpoint{
TxID: *targetTx.ID(),
Index: 0,
}
@@ -279,19 +278,19 @@ func TestCalcSequenceLock(t *testing.T) {
// Obtain the past median time from the PoV of the input created above.
// The past median time for the input is the past median time from the PoV
// of the block *prior* to the one that included it.
medianTime := node.RelativeAncestor(5).PastMedianTime(dag).UnixMilliseconds()
medianTime := node.RelativeAncestor(5).PastMedianTime(dag).Unix()
// The median time calculated from the PoV of the best block in the
// test DAG. For unconfirmed inputs, this value will be used since
// the MTP will be calculated from the PoV of the yet-to-be-mined
// block.
nextMedianTime := node.PastMedianTime(dag).UnixMilliseconds()
nextMedianTime := node.PastMedianTime(dag).Unix()
nextBlockBlueScore := int32(numBlocksToGenerate) + 1
// Add an additional transaction which will serve as our unconfirmed
// output.
unConfTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, nil, []*domainmessage.TxOut{{ScriptPubKey: nil, Value: 5}})
unConfUtxo := domainmessage.Outpoint{
unConfTx := wire.NewNativeMsgTx(wire.TxVersion, nil, []*wire.TxOut{{ScriptPubKey: nil, Value: 5}})
unConfUtxo := wire.Outpoint{
TxID: *unConfTx.TxID(),
Index: 0,
}
@@ -303,8 +302,9 @@ func TestCalcSequenceLock(t *testing.T) {
tests := []struct {
name string
tx *domainmessage.MsgTx
tx *wire.MsgTx
utxoSet UTXOSet
mempool bool
want *SequenceLock
}{
// A transaction with a single input with max sequence number.
@@ -312,37 +312,38 @@ func TestCalcSequenceLock(t *testing.T) {
// should be disabled.
{
name: "single input, max sequence number",
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: domainmessage.MaxTxInSequenceNum}}, nil),
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: wire.MaxTxInSequenceNum}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: -1,
Seconds: -1,
BlockBlueScore: -1,
},
},
// A transaction with a single input whose lock time is
// expressed in seconds. However, the specified lock time is
// below the required floor for time based lock times since
// they have time granularity of 524288 milliseconds. As a result, the
// milliseconds lock-time should be just before the median time of
// they have time granularity of 512 seconds. As a result, the
// seconds lock-time should be just before the median time of
// the targeted block.
{
name: "single input, milliseconds lock time below time granularity",
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 2)}}, nil),
name: "single input, seconds lock time below time granularity",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 2)}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime - 1,
Seconds: medianTime - 1,
BlockBlueScore: -1,
},
},
// A transaction with a single input whose lock time is
// expressed in seconds. The number of seconds should be 1048575
// milliseconds after the median past time of the DAG.
// expressed in seconds. The number of seconds should be 1023
// seconds after the median past time of the last block in the
// chain.
{
name: "single input, 1048575 milliseconds after median time",
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil),
name: "single input, 1023 seconds after median time",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 1024)}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime + 1048575,
Seconds: medianTime + 1023,
BlockBlueScore: -1,
},
},
@@ -354,22 +355,22 @@ func TestCalcSequenceLock(t *testing.T) {
// latest lock that isn't disabled.
{
name: "multiple varied inputs",
tx: domainmessage.NewNativeMsgTx(1,
[]*domainmessage.TxIn{{
tx: wire.NewNativeMsgTx(1,
[]*wire.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 2621440),
Sequence: LockTimeToSequence(true, 2560),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 4),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 5) |
domainmessage.SequenceLockTimeDisabled,
wire.SequenceLockTimeDisabled,
}},
nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime + (5 << domainmessage.SequenceLockTimeGranularity) - 1,
Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
BlockBlueScore: int64(prevUtxoBlueScore) + 3,
},
},
@@ -379,10 +380,10 @@ func TestCalcSequenceLock(t *testing.T) {
// height of 2 meaning it can be included at height 3.
{
name: "single input, lock-time in blocks",
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(false, 3)}}, nil),
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(false, 3)}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: -1,
Seconds: -1,
BlockBlueScore: int64(prevUtxoBlueScore) + 2,
},
},
@@ -391,16 +392,16 @@ func TestCalcSequenceLock(t *testing.T) {
// be the time further in the future.
{
name: "two inputs, lock-times in seconds",
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 5242880),
Sequence: LockTimeToSequence(true, 5120),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 2621440),
Sequence: LockTimeToSequence(true, 2560),
}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime + (10 << domainmessage.SequenceLockTimeGranularity) - 1,
Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
BlockBlueScore: -1,
},
},
@@ -410,8 +411,8 @@ func TestCalcSequenceLock(t *testing.T) {
// indicating it can be included at height 11.
{
name: "two inputs, lock-times in blocks",
tx: domainmessage.NewNativeMsgTx(1,
[]*domainmessage.TxIn{{
tx: wire.NewNativeMsgTx(1,
[]*wire.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 1),
}, {
@@ -421,7 +422,7 @@ func TestCalcSequenceLock(t *testing.T) {
nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: -1,
Seconds: -1,
BlockBlueScore: int64(prevUtxoBlueScore) + 10,
},
},
@@ -430,13 +431,13 @@ func TestCalcSequenceLock(t *testing.T) {
// further into the future for both inputs should be chosen.
{
name: "four inputs, two lock-times in time, two lock-times in blocks",
tx: domainmessage.NewNativeMsgTx(1,
[]*domainmessage.TxIn{{
tx: wire.NewNativeMsgTx(1,
[]*wire.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 2621440),
Sequence: LockTimeToSequence(true, 2560),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 6815744),
Sequence: LockTimeToSequence(true, 6656),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 3),
@@ -447,7 +448,7 @@ func TestCalcSequenceLock(t *testing.T) {
nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime + (13 << domainmessage.SequenceLockTimeGranularity) - 1,
Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
BlockBlueScore: int64(prevUtxoBlueScore) + 8,
},
},
@@ -459,10 +460,11 @@ func TestCalcSequenceLock(t *testing.T) {
// after that.
{
name: "single input, unconfirmed, lock-time in blocks",
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil),
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil),
utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{
Milliseconds: -1,
Seconds: -1,
BlockBlueScore: int64(nextBlockBlueScore) + 1,
},
},
@@ -470,11 +472,12 @@ func TestCalcSequenceLock(t *testing.T) {
// a time based lock, so the lock time should be based off the
// MTP of the *next* block.
{
name: "single input, unconfirmed, lock-time in milliseoncds",
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil),
name: "single input, unconfirmed, lock-time in seoncds",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1024)}}, nil),
utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{
Milliseconds: nextMedianTime + 1048575,
Seconds: nextMedianTime + 1023,
BlockBlueScore: -1,
},
},
@@ -483,14 +486,14 @@ func TestCalcSequenceLock(t *testing.T) {
t.Logf("Running %v SequenceLock tests", len(tests))
for _, test := range tests {
utilTx := util.NewTx(test.tx)
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet)
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet, test.mempool)
if err != nil {
t.Fatalf("test '%s', unable to calc sequence lock: %v", test.name, err)
}
if seqLock.Milliseconds != test.want.Milliseconds {
t.Fatalf("test '%s' got %v milliseconds want %v milliseconds",
test.name, seqLock.Milliseconds, test.want.Milliseconds)
if seqLock.Seconds != test.want.Seconds {
t.Fatalf("test '%s' got %v seconds want %v seconds",
test.name, seqLock.Seconds, test.want.Seconds)
}
if seqLock.BlockBlueScore != test.want.BlockBlueScore {
t.Fatalf("test '%s' got blue score of %v want blue score of %v ",
@@ -516,35 +519,31 @@ func TestCalcPastMedianTime(t *testing.T) {
}
tests := []struct {
blockNumber uint32
expectedMillisecondsSinceGenesis int64
blockNumber uint32
expectedSecondsSinceGenesis int64
}{
{
blockNumber: 262,
expectedMillisecondsSinceGenesis: 130000,
blockNumber: 262,
expectedSecondsSinceGenesis: 130,
},
{
blockNumber: 270,
expectedMillisecondsSinceGenesis: 138000,
blockNumber: 270,
expectedSecondsSinceGenesis: 138,
},
{
blockNumber: 240,
expectedMillisecondsSinceGenesis: 108000,
blockNumber: 240,
expectedSecondsSinceGenesis: 108,
},
{
blockNumber: 5,
expectedMillisecondsSinceGenesis: 0,
blockNumber: 5,
expectedSecondsSinceGenesis: 0,
},
}
for _, test := range tests {
millisecondsSinceGenesis := nodes[test.blockNumber].PastMedianTime(dag).UnixMilliseconds() -
dag.genesis.Header().Timestamp.UnixMilliseconds()
if millisecondsSinceGenesis != test.expectedMillisecondsSinceGenesis {
t.Errorf("TestCalcPastMedianTime: expected past median time of block %v to be %v milliseconds "+
"from genesis but got %v",
test.blockNumber, test.expectedMillisecondsSinceGenesis, millisecondsSinceGenesis)
secondsSinceGenesis := nodes[test.blockNumber].PastMedianTime(dag).Unix() - dag.genesis.Header().Timestamp.Unix()
if secondsSinceGenesis != test.expectedSecondsSinceGenesis {
t.Errorf("TestCalcPastMedianTime: expected past median time of block %v to be %v seconds from genesis but got %v", test.blockNumber, test.expectedSecondsSinceGenesis, secondsSinceGenesis)
}
}
}
@@ -554,19 +553,18 @@ func TestNew(t *testing.T) {
dbPath := filepath.Join(tempDir, "TestNew")
_ = os.RemoveAll(dbPath)
databaseContext, err := dbaccess.New(dbPath)
err := dbaccess.Open(dbPath)
if err != nil {
t.Fatalf("error creating db: %s", err)
}
defer func() {
databaseContext.Close()
dbaccess.Close()
os.RemoveAll(dbPath)
}()
config := &Config{
DatabaseContext: databaseContext,
DAGParams: &dagconfig.SimnetParams,
TimeSource: NewTimeSource(),
SigCache: txscript.NewSigCache(1000),
DAGParams: &dagconfig.SimnetParams,
TimeSource: NewTimeSource(),
SigCache: txscript.NewSigCache(1000),
}
_, err = New(config)
if err != nil {
@@ -594,21 +592,20 @@ func TestAcceptingInInit(t *testing.T) {
// Create a test database
dbPath := filepath.Join(tempDir, "TestAcceptingInInit")
_ = os.RemoveAll(dbPath)
databaseContext, err := dbaccess.New(dbPath)
err := dbaccess.Open(dbPath)
if err != nil {
t.Fatalf("error creating db: %s", err)
}
defer func() {
databaseContext.Close()
dbaccess.Close()
os.RemoveAll(dbPath)
}()
// Create a DAG to add the test block into
config := &Config{
DatabaseContext: databaseContext,
DAGParams: &dagconfig.SimnetParams,
TimeSource: NewTimeSource(),
SigCache: txscript.NewSigCache(1000),
DAGParams: &dagconfig.SimnetParams,
TimeSource: NewTimeSource(),
SigCache: txscript.NewSigCache(1000),
}
dag, err := New(config)
if err != nil {
@@ -624,15 +621,12 @@ func TestAcceptingInInit(t *testing.T) {
testBlock := blocks[1]
// Create a test blockNode with an unvalidated status
genesisNode, ok := dag.index.LookupNode(genesisBlock.Hash())
if !ok {
t.Fatalf("genesis block does not exist in the DAG")
}
genesisNode := dag.index.LookupNode(genesisBlock.Hash())
testNode, _ := dag.newBlockNode(&testBlock.MsgBlock().Header, blockSetFromSlice(genesisNode))
testNode.status = statusDataStored
// Manually add the test block to the database
dbTx, err := databaseContext.NewTx()
dbTx, err := dbaccess.NewTx()
if err != nil {
t.Fatalf("Failed to open database "+
"transaction: %s", err)
@@ -665,11 +659,7 @@ func TestAcceptingInInit(t *testing.T) {
}
// Make sure that the test node's status is valid
testNode, ok = dag.index.LookupNode(testBlock.Hash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", testBlock.Hash())
}
testNode = dag.index.LookupNode(testBlock.Hash())
if testNode.status&statusValid == 0 {
t.Fatalf("testNode is unexpectedly invalid")
}
@@ -698,8 +688,8 @@ func TestConfirmations(t *testing.T) {
}
// Add a chain of blocks
chainBlocks := make([]*domainmessage.MsgBlock, 5)
chainBlocks[0] = dag.Params.GenesisBlock
chainBlocks := make([]*wire.MsgBlock, 5)
chainBlocks[0] = dag.dagParams.GenesisBlock
for i := uint32(1); i < 5; i++ {
chainBlocks[i] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[i-1])
}
@@ -718,7 +708,7 @@ func TestConfirmations(t *testing.T) {
}
}
branchingBlocks := make([]*domainmessage.MsgBlock, 2)
branchingBlocks := make([]*wire.MsgBlock, 2)
// Add two branching blocks
branchingBlocks[0] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[1])
branchingBlocks[1] = prepareAndProcessBlockByParentMsgBlocks(t, dag, branchingBlocks[0])
@@ -791,7 +781,7 @@ func TestAcceptingBlock(t *testing.T) {
defer teardownFunc()
dag.TestSetCoinbaseMaturity(0)
acceptingBlockByMsgBlock := func(block *domainmessage.MsgBlock) (*blockNode, error) {
acceptingBlockByMsgBlock := func(block *wire.MsgBlock) (*blockNode, error) {
node := nodeByMsgBlock(t, dag, block)
return dag.acceptingBlock(node)
}
@@ -807,8 +797,8 @@ func TestAcceptingBlock(t *testing.T) {
}
numChainBlocks := uint32(10)
chainBlocks := make([]*domainmessage.MsgBlock, numChainBlocks)
chainBlocks[0] = dag.Params.GenesisBlock
chainBlocks := make([]*wire.MsgBlock, numChainBlocks)
chainBlocks[0] = dag.dagParams.GenesisBlock
for i := uint32(1); i <= numChainBlocks-1; i++ {
chainBlocks[i] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[i-1])
}
@@ -924,7 +914,7 @@ func testFinalizeNodesBelowFinalityPoint(t *testing.T, deleteDiffData bool) {
blockTime := dag.genesis.Header().Timestamp
flushUTXODiffStore := func() {
dbTx, err := dag.databaseContext.NewTx()
dbTx, err := dbaccess.NewTx()
if err != nil {
t.Fatalf("Failed to open database transaction: %s", err)
}
@@ -954,7 +944,7 @@ func testFinalizeNodesBelowFinalityPoint(t *testing.T, deleteDiffData bool) {
flushUTXODiffStore()
return node
}
finalityInterval := dag.FinalityInterval()
finalityInterval := dag.dagParams.FinalityInterval
nodes := make([]*blockNode, 0, finalityInterval)
currentNode := dag.genesis
nodes = append(nodes, currentNode)
@@ -1027,11 +1017,11 @@ func TestDAGIndexFailedStatus(t *testing.T) {
}
defer teardownFunc()
invalidCbTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{}, []*domainmessage.TxOut{}, subnetworkid.SubnetworkIDCoinbase, 0, []byte{})
invalidCbTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{}, []*wire.TxOut{}, subnetworkid.SubnetworkIDCoinbase, 0, []byte{})
txs := []*util.Tx{util.NewTx(invalidCbTx)}
hashMerkleRoot := BuildHashMerkleTreeStore(txs).Root()
invalidMsgBlock := domainmessage.NewMsgBlock(
domainmessage.NewBlockHeader(
invalidMsgBlock := wire.NewMsgBlock(
wire.NewBlockHeader(
1,
[]*daghash.Hash{params.GenesisHash}, hashMerkleRoot,
&daghash.Hash{},
@@ -1055,16 +1045,16 @@ func TestDAGIndexFailedStatus(t *testing.T) {
"is an orphan\n")
}
invalidBlockNode, ok := dag.index.LookupNode(invalidBlock.Hash())
if !ok {
invalidBlockNode := dag.index.LookupNode(invalidBlock.Hash())
if invalidBlockNode == nil {
t.Fatalf("invalidBlockNode wasn't added to the block index as expected")
}
if invalidBlockNode.status&statusValidateFailed != statusValidateFailed {
t.Fatalf("invalidBlockNode status to have %b flags raised (got: %b)", statusValidateFailed, invalidBlockNode.status)
}
invalidMsgBlockChild := domainmessage.NewMsgBlock(
domainmessage.NewBlockHeader(1, []*daghash.Hash{
invalidMsgBlockChild := wire.NewMsgBlock(
wire.NewBlockHeader(1, []*daghash.Hash{
invalidBlock.Hash(),
}, hashMerkleRoot, &daghash.Hash{}, &daghash.Hash{}, dag.genesis.bits, 0),
)
@@ -1084,16 +1074,16 @@ func TestDAGIndexFailedStatus(t *testing.T) {
t.Fatalf("ProcessBlock incorrectly returned invalidBlockChild " +
"is an orphan\n")
}
invalidBlockChildNode, ok := dag.index.LookupNode(invalidBlockChild.Hash())
if !ok {
invalidBlockChildNode := dag.index.LookupNode(invalidBlockChild.Hash())
if invalidBlockChildNode == nil {
t.Fatalf("invalidBlockChild wasn't added to the block index as expected")
}
if invalidBlockChildNode.status&statusInvalidAncestor != statusInvalidAncestor {
t.Fatalf("invalidBlockNode status to have %b flags raised (got %b)", statusInvalidAncestor, invalidBlockChildNode.status)
}
invalidMsgBlockGrandChild := domainmessage.NewMsgBlock(
domainmessage.NewBlockHeader(1, []*daghash.Hash{
invalidMsgBlockGrandChild := wire.NewMsgBlock(
wire.NewBlockHeader(1, []*daghash.Hash{
invalidBlockChild.Hash(),
}, hashMerkleRoot, &daghash.Hash{}, &daghash.Hash{}, dag.genesis.bits, 0),
)
@@ -1112,8 +1102,8 @@ func TestDAGIndexFailedStatus(t *testing.T) {
t.Fatalf("ProcessBlock incorrectly returned invalidBlockGrandChild " +
"is an orphan\n")
}
invalidBlockGrandChildNode, ok := dag.index.LookupNode(invalidBlockGrandChild.Hash())
if !ok {
invalidBlockGrandChildNode := dag.index.LookupNode(invalidBlockGrandChild.Hash())
if invalidBlockGrandChildNode == nil {
t.Fatalf("invalidBlockGrandChild wasn't added to the block index as expected")
}
if invalidBlockGrandChildNode.status&statusInvalidAncestor != statusInvalidAncestor {
@@ -1121,7 +1111,22 @@ func TestDAGIndexFailedStatus(t *testing.T) {
}
}
func testProcessBlockRuleError(t *testing.T, dag *BlockDAG, block *domainmessage.MsgBlock, expectedRuleErr error) {
func TestIsDAGCurrentMaxDiff(t *testing.T) {
netParams := []*dagconfig.Params{
&dagconfig.MainnetParams,
&dagconfig.TestnetParams,
&dagconfig.DevnetParams,
&dagconfig.RegressionNetParams,
&dagconfig.SimnetParams,
}
for _, params := range netParams {
if params.TargetTimePerBlock*time.Duration(params.FinalityInterval) < isDAGCurrentMaxDiff {
t.Errorf("in %s, a DAG can be considered current even if it's below the finality point", params.Name)
}
}
}
func testProcessBlockRuleError(t *testing.T, dag *BlockDAG, block *wire.MsgBlock, expectedRuleErr error) {
isOrphan, isDelayed, err := dag.ProcessBlock(util.NewBlock(block), BFNoPoWCheck)
err = checkRuleError(err, expectedRuleErr)
@@ -1157,24 +1162,24 @@ func TestDoubleSpends(t *testing.T) {
if err != nil {
t.Fatalf("Failed to build signature script: %s", err)
}
txIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
txIn := &wire.TxIn{
PreviousOutpoint: wire.Outpoint{TxID: *cbTx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: domainmessage.MaxTxInSequenceNum,
Sequence: wire.MaxTxInSequenceNum,
}
txOut := &domainmessage.TxOut{
txOut := &wire.TxOut{
ScriptPubKey: OpTrueScript,
Value: uint64(1),
}
tx1 := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut})
tx1 := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
doubleSpendTxOut := &domainmessage.TxOut{
doubleSpendTxOut := &wire.TxOut{
ScriptPubKey: OpTrueScript,
Value: uint64(2),
}
doubleSpendTx1 := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{doubleSpendTxOut})
doubleSpendTx1 := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{doubleSpendTxOut})
blockWithTx1 := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*domainmessage.MsgTx{tx1})
blockWithTx1 := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*wire.MsgTx{tx1})
// Check that a block will be rejected if it has a transaction that already exists in its past.
anotherBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{blockWithTx1.BlockHash()}, nil)
@@ -1209,7 +1214,7 @@ func TestDoubleSpends(t *testing.T) {
testProcessBlockRuleError(t, dag, blockWithDoubleSpendForTx1, ruleError(ErrMissingTxOut, ""))
blockInAnticoneOfBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*domainmessage.MsgTx{doubleSpendTx1})
blockInAnticoneOfBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*wire.MsgTx{doubleSpendTx1})
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -1252,7 +1257,7 @@ func TestDoubleSpends(t *testing.T) {
func TestUTXOCommitment(t *testing.T) {
// Create a new database and dag instance to run tests against.
params := dagconfig.SimnetParams
params := dagconfig.DevnetParams
params.BlockCoinbaseMaturity = 0
dag, teardownFunc, err := DAGSetup("TestUTXOCommitment", true, Config{
DAGParams: &params,
@@ -1264,7 +1269,7 @@ func TestUTXOCommitment(t *testing.T) {
resetExtraNonceForTest()
createTx := func(txToSpend *domainmessage.MsgTx) *domainmessage.MsgTx {
createTx := func(txToSpend *wire.MsgTx) *wire.MsgTx {
scriptPubKey, err := txscript.PayToScriptHashScript(OpTrueScript)
if err != nil {
t.Fatalf("TestUTXOCommitment: failed to build script pub key: %s", err)
@@ -1273,16 +1278,16 @@ func TestUTXOCommitment(t *testing.T) {
if err != nil {
t.Fatalf("TestUTXOCommitment: failed to build signature script: %s", err)
}
txIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *txToSpend.TxID(), Index: 0},
txIn := &wire.TxIn{
PreviousOutpoint: wire.Outpoint{TxID: *txToSpend.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: domainmessage.MaxTxInSequenceNum,
Sequence: wire.MaxTxInSequenceNum,
}
txOut := &domainmessage.TxOut{
txOut := &wire.TxOut{
ScriptPubKey: scriptPubKey,
Value: uint64(1),
}
return domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut})
return wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
}
// Build the following DAG:
@@ -1298,17 +1303,17 @@ func TestUTXOCommitment(t *testing.T) {
// Block C:
txSpendBlockACoinbase := createTx(blockA.Transactions[0])
blockCTxs := []*domainmessage.MsgTx{txSpendBlockACoinbase}
blockCTxs := []*wire.MsgTx{txSpendBlockACoinbase}
blockC := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockA.BlockHash()}, blockCTxs)
// Block D:
txSpendTxInBlockC := createTx(txSpendBlockACoinbase)
blockDTxs := []*domainmessage.MsgTx{txSpendTxInBlockC}
blockDTxs := []*wire.MsgTx{txSpendTxInBlockC}
blockD := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockB.BlockHash(), blockC.BlockHash()}, blockDTxs)
// Get the pastUTXO of blockD
blockNodeD, ok := dag.index.LookupNode(blockD.BlockHash())
if !ok {
blockNodeD := dag.index.LookupNode(blockD.BlockHash())
if blockNodeD == nil {
t.Fatalf("TestUTXOCommitment: blockNode for block D not found")
}
blockDPastUTXO, _, _, _ := dag.pastUTXO(blockNodeD)
@@ -1367,8 +1372,8 @@ func TestPastUTXOMultiSet(t *testing.T) {
blockC := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockB.BlockHash()}, nil)
// Take blockC's selectedParentMultiset
blockNodeC, ok := dag.index.LookupNode(blockC.BlockHash())
if !ok {
blockNodeC := dag.index.LookupNode(blockC.BlockHash())
if blockNodeC == nil {
t.Fatalf("TestPastUTXOMultiSet: blockNode for blockC not found")
}
blockCSelectedParentMultiset, err := blockNodeC.selectedParentMultiset(dag)

View File

@@ -11,15 +11,15 @@ import (
"fmt"
"io"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/binaryserializer"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
)
var (
@@ -28,19 +28,19 @@ var (
byteOrder = binary.LittleEndian
)
// ErrNotInDAG signifies that a block hash that is not in the
// errNotInDAG signifies that a block hash or height that is not in the
// DAG was requested.
type ErrNotInDAG string
type errNotInDAG string
// Error implements the error interface.
func (e ErrNotInDAG) Error() string {
func (e errNotInDAG) Error() string {
return string(e)
}
// IsNotInDAGErr returns whether or not the passed error is an
// ErrNotInDAG error.
func IsNotInDAGErr(err error) bool {
var notInDAGErr ErrNotInDAG
// isNotInDAGErr returns whether or not the passed error is an
// errNotInDAG error.
func isNotInDAGErr(err error) bool {
var notInDAGErr errNotInDAG
return errors.As(err, &notInDAGErr)
}
@@ -49,7 +49,7 @@ func IsNotInDAGErr(err error) bool {
// keys will be iterated in an ascending order by the outpoint index.
var outpointIndexByteOrder = binary.BigEndian
func serializeOutpoint(w io.Writer, outpoint *domainmessage.Outpoint) error {
func serializeOutpoint(w io.Writer, outpoint *wire.Outpoint) error {
_, err := w.Write(outpoint.TxID[:])
if err != nil {
return err
@@ -61,10 +61,10 @@ func serializeOutpoint(w io.Writer, outpoint *domainmessage.Outpoint) error {
var outpointSerializeSize = daghash.TxIDSize + 4
// deserializeOutpoint decodes an outpoint from the passed serialized byte
// slice into a new domainmessage.Outpoint using a format that is suitable for long-
// slice into a new wire.Outpoint using a format that is suitable for long-
// term storage. This format is described in detail above.
func deserializeOutpoint(r io.Reader) (*domainmessage.Outpoint, error) {
outpoint := &domainmessage.Outpoint{}
func deserializeOutpoint(r io.Reader) (*wire.Outpoint, error) {
outpoint := &wire.Outpoint{}
_, err := r.Read(outpoint.TxID[:])
if err != nil {
return nil, err
@@ -164,9 +164,9 @@ func saveDAGState(dbContext dbaccess.Context, state *dagState) error {
// createDAGState initializes the DAG state to the
// genesis block and the node's local subnetwork id.
func (dag *BlockDAG) createDAGState(localSubnetworkID *subnetworkid.SubnetworkID) error {
return saveDAGState(dag.databaseContext, &dagState{
TipHashes: []*daghash.Hash{dag.Params.GenesisHash},
LastFinalityPoint: dag.Params.GenesisHash,
return saveDAGState(dbaccess.NoTx(), &dagState{
TipHashes: []*daghash.Hash{dag.dagParams.GenesisHash},
LastFinalityPoint: dag.dagParams.GenesisHash,
LocalSubnetworkID: localSubnetworkID,
})
}
@@ -177,7 +177,7 @@ func (dag *BlockDAG) createDAGState(localSubnetworkID *subnetworkid.SubnetworkID
func (dag *BlockDAG) initDAGState() error {
// Fetch the stored DAG state from the database. If it doesn't exist,
// it means that kaspad is running for the first time.
serializedDAGState, err := dbaccess.FetchDAGState(dag.databaseContext)
serializedDAGState, err := dbaccess.FetchDAGState(dbaccess.NoTx())
if dbaccess.IsNotFoundError(err) {
// Initialize the database and the DAG state to the genesis block.
return dag.createDAGState(dag.subnetworkID)
@@ -209,13 +209,13 @@ func (dag *BlockDAG) initDAGState() error {
}
log.Debugf("Loading reachability data...")
err = dag.reachabilityTree.init(dag.databaseContext)
err = dag.reachabilityStore.init(dbaccess.NoTx())
if err != nil {
return err
}
log.Debugf("Loading multiset data...")
err = dag.multisetStore.init(dag.databaseContext)
err = dag.multisetStore.init(dbaccess.NoTx())
if err != nil {
return err
}
@@ -233,12 +233,7 @@ func (dag *BlockDAG) initDAGState() error {
}
log.Debugf("Setting the last finality point...")
var ok bool
dag.lastFinalityPoint, ok = dag.index.LookupNode(dagState.LastFinalityPoint)
if !ok {
return errors.Errorf("finality point block %s "+
"does not exist in the DAG", dagState.LastFinalityPoint)
}
dag.lastFinalityPoint = dag.index.LookupNode(dagState.LastFinalityPoint)
dag.finalizeNodesBelowFinalityPoint(false)
log.Debugf("Processing unprocessed blockNodes...")
@@ -263,7 +258,7 @@ func (dag *BlockDAG) validateLocalSubnetworkID(state *dagState) error {
}
func (dag *BlockDAG) initBlockIndex() (unprocessedBlockNodes []*blockNode, err error) {
blockIndexCursor, err := dbaccess.BlockIndexCursor(dag.databaseContext)
blockIndexCursor, err := dbaccess.BlockIndexCursor(dbaccess.NoTx())
if err != nil {
return nil, err
}
@@ -293,7 +288,7 @@ func (dag *BlockDAG) initBlockIndex() (unprocessedBlockNodes []*blockNode, err e
}
if dag.blockCount == 0 {
if !node.hash.IsEqual(dag.Params.GenesisHash) {
if !node.hash.IsEqual(dag.dagParams.GenesisHash) {
return nil, errors.Errorf("Expected "+
"first entry in block index to be genesis block, "+
"found %s", node.hash)
@@ -317,7 +312,7 @@ func (dag *BlockDAG) initBlockIndex() (unprocessedBlockNodes []*blockNode, err e
func (dag *BlockDAG) initUTXOSet() (fullUTXOCollection utxoCollection, err error) {
fullUTXOCollection = make(utxoCollection)
cursor, err := dbaccess.UTXOSetCursor(dag.databaseContext)
cursor, err := dbaccess.UTXOSetCursor(dbaccess.NoTx())
if err != nil {
return nil, err
}
@@ -353,8 +348,8 @@ func (dag *BlockDAG) initUTXOSet() (fullUTXOCollection utxoCollection, err error
func (dag *BlockDAG) initVirtualBlockTips(state *dagState) error {
tips := newBlockSet()
for _, tipHash := range state.TipHashes {
tip, ok := dag.index.LookupNode(tipHash)
if !ok {
tip := dag.index.LookupNode(tipHash)
if tip == nil {
return errors.Errorf("cannot find "+
"DAG tip %s in block index", state.TipHashes)
}
@@ -368,7 +363,7 @@ func (dag *BlockDAG) processUnprocessedBlockNodes(unprocessedBlockNodes []*block
for _, node := range unprocessedBlockNodes {
// Check to see if the block exists in the block DB. If it
// doesn't, the database has certainly been corrupted.
blockExists, err := dbaccess.HasBlock(dag.databaseContext, node.hash)
blockExists, err := dbaccess.HasBlock(dbaccess.NoTx(), node.hash)
if err != nil {
return errors.Wrapf(err, "HasBlock "+
"for block %s failed: %s", node.hash, err)
@@ -379,7 +374,7 @@ func (dag *BlockDAG) processUnprocessedBlockNodes(unprocessedBlockNodes []*block
}
// Attempt to accept the block.
block, err := dag.fetchBlockByHash(node.hash)
block, err := fetchBlockByHash(dbaccess.NoTx(), node.hash)
if err != nil {
return err
}
@@ -410,7 +405,7 @@ func (dag *BlockDAG) processUnprocessedBlockNodes(unprocessedBlockNodes []*block
func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
buffer := bytes.NewReader(blockRow)
var header domainmessage.BlockHeader
var header wire.BlockHeader
err := header.Deserialize(buffer)
if err != nil {
return nil, err
@@ -421,7 +416,7 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
version: header.Version,
bits: header.Bits,
nonce: header.Nonce,
timestamp: header.Timestamp.UnixMilliseconds(),
timestamp: header.Timestamp.Unix(),
hashMerkleRoot: header.HashMerkleRoot,
acceptedIDMerkleRoot: header.AcceptedIDMerkleRoot,
utxoCommitment: header.UTXOCommitment,
@@ -431,8 +426,8 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
node.parents = newBlockSet()
for _, hash := range header.ParentHashes {
parent, ok := dag.index.LookupNode(hash)
if !ok {
parent := dag.index.LookupNode(hash)
if parent == nil {
return nil, errors.Errorf("deserializeBlockNode: Could "+
"not find parent %s for block %s", hash, header.BlockHash())
}
@@ -452,11 +447,7 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
// Because genesis doesn't have selected parent, it's serialized as zero hash
if !selectedParentHash.IsEqual(&daghash.ZeroHash) {
var ok bool
node.selectedParent, ok = dag.index.LookupNode(selectedParentHash)
if !ok {
return nil, errors.Errorf("block %s does not exist in the DAG", selectedParentHash)
}
node.selectedParent = dag.index.LookupNode(selectedParentHash)
}
node.blueScore, err = binaryserializer.Uint64(buffer, byteOrder)
@@ -464,7 +455,7 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
return nil, err
}
bluesCount, err := domainmessage.ReadVarInt(buffer)
bluesCount, err := wire.ReadVarInt(buffer)
if err != nil {
return nil, err
}
@@ -475,15 +466,10 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
if _, err := io.ReadFull(buffer, hash[:]); err != nil {
return nil, err
}
var ok bool
node.blues[i], ok = dag.index.LookupNode(hash)
if !ok {
return nil, errors.Errorf("block %s does not exist in the DAG", selectedParentHash)
}
node.blues[i] = dag.index.LookupNode(hash)
}
bluesAnticoneSizesLen, err := domainmessage.ReadVarInt(buffer)
bluesAnticoneSizesLen, err := wire.ReadVarInt(buffer)
if err != nil {
return nil, err
}
@@ -498,8 +484,8 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
if err != nil {
return nil, err
}
blue, ok := dag.index.LookupNode(hash)
if !ok {
blue := dag.index.LookupNode(hash)
if blue == nil {
return nil, errors.Errorf("couldn't find block with hash %s", hash)
}
node.bluesAnticoneSizes[blue] = dagconfig.KType(bluesAnticoneSize)
@@ -510,8 +496,8 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
// fetchBlockByHash retrieves the raw block for the provided hash,
// deserializes it, and returns a util.Block of it.
func (dag *BlockDAG) fetchBlockByHash(hash *daghash.Hash) (*util.Block, error) {
blockBytes, err := dbaccess.FetchBlock(dag.databaseContext, hash)
func fetchBlockByHash(dbContext dbaccess.Context, hash *daghash.Hash) (*util.Block, error) {
blockBytes, err := dbaccess.FetchBlock(dbContext, hash)
if err != nil {
return nil, err
}
@@ -527,7 +513,7 @@ func storeBlock(dbContext *dbaccess.TxContext, block *util.Block) error {
}
func serializeBlockNode(node *blockNode) ([]byte, error) {
w := bytes.NewBuffer(make([]byte, 0, domainmessage.MaxBlockHeaderPayload+1))
w := bytes.NewBuffer(make([]byte, 0, wire.MaxBlockHeaderPayload+1))
header := node.Header()
err := header.Serialize(w)
if err != nil {
@@ -554,7 +540,7 @@ func serializeBlockNode(node *blockNode) ([]byte, error) {
return nil, err
}
err = domainmessage.WriteVarInt(w, uint64(len(node.blues)))
err = wire.WriteVarInt(w, uint64(len(node.blues)))
if err != nil {
return nil, err
}
@@ -566,7 +552,7 @@ func serializeBlockNode(node *blockNode) ([]byte, error) {
}
}
err = domainmessage.WriteVarInt(w, uint64(len(node.bluesAnticoneSizes)))
err = wire.WriteVarInt(w, uint64(len(node.bluesAnticoneSizes)))
if err != nil {
return nil, err
}
@@ -604,13 +590,13 @@ func blockHashFromBlockIndexKey(BlockIndexKey []byte) (*daghash.Hash, error) {
// This function is safe for concurrent access.
func (dag *BlockDAG) BlockByHash(hash *daghash.Hash) (*util.Block, error) {
// Lookup the block hash in block index and ensure it is in the DAG
node, ok := dag.index.LookupNode(hash)
if !ok {
node := dag.index.LookupNode(hash)
if node == nil {
str := fmt.Sprintf("block %s is not in the DAG", hash)
return nil, ErrNotInDAG(str)
return nil, errNotInDAG(str)
}
block, err := dag.fetchBlockByHash(node.hash)
block, err := fetchBlockByHash(dbaccess.NoTx(), node.hash)
if err != nil {
return nil, err
}
@@ -639,7 +625,7 @@ func (dag *BlockDAG) BlockHashesFrom(lowHash *daghash.Hash, limit int) ([]*dagha
}
key := blockIndexKey(lowHash, blueScore)
cursor, err := dbaccess.BlockIndexCursorFrom(dag.databaseContext, key)
cursor, err := dbaccess.BlockIndexCursorFrom(dbaccess.NoTx(), key)
if dbaccess.IsNotFoundError(err) {
return nil, errors.Wrapf(err, "block %s not in block index", lowHash)
}
@@ -662,16 +648,3 @@ func (dag *BlockDAG) BlockHashesFrom(lowHash *daghash.Hash, limit int) ([]*dagha
return blockHashes, nil
}
func (dag *BlockDAG) fetchBlueBlocks(node *blockNode) ([]*util.Block, error) {
blueBlocks := make([]*util.Block, len(node.blues))
for i, blueBlockNode := range node.blues {
blueBlock, err := dag.fetchBlockByHash(blueBlockNode.hash)
if err != nil {
return nil, err
}
blueBlocks[i] = blueBlock
}
return blueBlocks, nil
}

View File

@@ -14,25 +14,25 @@ import (
"github.com/kaspanet/kaspad/util/daghash"
)
// TestErrNotInDAG ensures the functions related to ErrNotInDAG work
// TestErrNotInDAG ensures the functions related to errNotInDAG work
// as expected.
func TestErrNotInDAG(t *testing.T) {
errStr := "no block at height 1 exists"
err := error(ErrNotInDAG(errStr))
err := error(errNotInDAG(errStr))
// Ensure the stringized output for the error is as expected.
if err.Error() != errStr {
t.Fatalf("ErrNotInDAG retuned unexpected error string - "+
t.Fatalf("errNotInDAG retuned unexpected error string - "+
"got %q, want %q", err.Error(), errStr)
}
// Ensure error is detected as the correct type.
if !IsNotInDAGErr(err) {
t.Fatalf("IsNotInDAGErr did not detect as expected type")
if !isNotInDAGErr(err) {
t.Fatalf("isNotInDAGErr did not detect as expected type")
}
err = errors.New("something else")
if IsNotInDAGErr(err) {
t.Fatalf("IsNotInDAGErr detected incorrect type")
if isNotInDAGErr(err) {
t.Fatalf("isNotInDAGErr detected incorrect type")
}
}

View File

@@ -6,16 +6,16 @@ package blockdag
import (
"github.com/kaspanet/kaspad/util/bigintpool"
"github.com/kaspanet/kaspad/util/mstime"
"time"
"github.com/kaspanet/kaspad/util"
)
// requiredDifficulty calculates the required difficulty for a
// block given its bluest parent.
func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime mstime.Time) uint32 {
func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime time.Time) uint32 {
// Genesis block.
if dag.Params.DisableDifficultyAdjustment || bluestParent == nil || bluestParent.blueScore < dag.difficultyAdjustmentWindowSize+1 {
if bluestParent == nil || bluestParent.blueScore < dag.difficultyAdjustmentWindowSize+1 {
return dag.powMaxBits
}
@@ -34,7 +34,7 @@ func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime ms
defer bigintpool.Release(newTarget)
windowTimeStampDifference := bigintpool.Acquire(windowMaxTimeStamp - windowMinTimestamp)
defer bigintpool.Release(windowTimeStampDifference)
targetTimePerBlock := bigintpool.Acquire(dag.Params.TargetTimePerBlock.Milliseconds())
targetTimePerBlock := bigintpool.Acquire(dag.targetTimePerBlock)
defer bigintpool.Release(targetTimePerBlock)
difficultyAdjustmentWindowSize := bigintpool.Acquire(int64(dag.difficultyAdjustmentWindowSize))
defer bigintpool.Release(difficultyAdjustmentWindowSize)
@@ -44,7 +44,7 @@ func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime ms
Mul(newTarget, windowTimeStampDifference).
Div(newTarget, targetTimePerBlock).
Div(newTarget, difficultyAdjustmentWindowSize)
if newTarget.Cmp(dag.Params.PowMax) > 0 {
if newTarget.Cmp(dag.dagParams.PowMax) > 0 {
return dag.powMaxBits
}
newTargetBits := util.BigToCompact(newTarget)
@@ -55,7 +55,7 @@ func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime ms
// be built on top of the current tips.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) NextRequiredDifficulty(timestamp mstime.Time) uint32 {
func (dag *BlockDAG) NextRequiredDifficulty(timestamp time.Time) uint32 {
difficulty := dag.requiredDifficulty(dag.virtual.parents.bluest(), timestamp)
return difficulty
}

View File

@@ -5,8 +5,7 @@
package blockdag
import (
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/dagconfig"
"math/big"
"testing"
"time"
@@ -80,7 +79,7 @@ func TestCalcWork(t *testing.T) {
}
func TestDifficulty(t *testing.T) {
params := dagconfig.MainnetParams
params := dagconfig.SimnetParams
params.K = 1
params.DifficultyAdjustmentWindowSize = 264
dag, teardownFunc, err := DAGSetup("TestDifficulty", true, Config{
@@ -91,11 +90,11 @@ func TestDifficulty(t *testing.T) {
}
defer teardownFunc()
zeroTime := mstime.Time{}
addNode := func(parents blockSet, blockTime mstime.Time) *blockNode {
zeroTime := time.Unix(0, 0)
addNode := func(parents blockSet, blockTime time.Time) *blockNode {
bluestParent := parents.bluest()
if blockTime.IsZero() {
blockTime = bluestParent.time()
if blockTime == zeroTime {
blockTime = time.Unix(bluestParent.timestamp, 0)
blockTime = blockTime.Add(params.TargetTimePerBlock)
}
block, err := PrepareBlockForTest(dag, parents.hashes(), nil)
@@ -115,11 +114,7 @@ func TestDifficulty(t *testing.T) {
if isOrphan {
t.Fatalf("block was unexpectedly orphan")
}
node, ok := dag.index.LookupNode(block.BlockHash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", block.BlockHash())
}
return node
return dag.index.LookupNode(block.BlockHash())
}
tip := dag.genesis
for i := uint64(0); i < dag.difficultyAdjustmentWindowSize; i++ {
@@ -175,7 +170,7 @@ func TestDifficulty(t *testing.T) {
sameBitsCount = 0
}
}
slowBlockTime := tip.time()
slowBlockTime := time.Unix(tip.timestamp, 0)
slowBlockTime = slowBlockTime.Add(params.TargetTimePerBlock + time.Second)
slowNode := addNode(blockSetFromSlice(tip), slowBlockTime)
if slowNode.bits != tip.bits {

View File

@@ -28,6 +28,11 @@ const (
// to a newer version.
ErrBlockVersionTooOld
// ErrInvalidTime indicates the time in the passed block has a precision
// that is more than one second. The DAG consensus rules require
// timestamps to have a maximum precision of one second.
ErrInvalidTime
// ErrTimeTooOld indicates the time is either before the median time of
// the last several blocks per the DAG consensus rules.
ErrTimeTooOld
@@ -64,9 +69,6 @@ const (
// the expected value.
ErrBadUTXOCommitment
// ErrInvalidSubnetwork indicates the subnetwork is now allowed.
ErrInvalidSubnetwork
// ErrFinalityPointTimeTooOld indicates a block has a timestamp before the
// last finality point.
ErrFinalityPointTimeTooOld
@@ -206,10 +208,6 @@ const (
// ErrDelayedBlockIsNotAllowed indicates that a block with a delayed timestamp was
// submitted with BFDisallowDelay flag raised.
ErrDelayedBlockIsNotAllowed
// ErrOrphanBlockIsNotAllowed indicates that an orphan block was submitted with
// BFDisallowOrphans flag raised.
ErrOrphanBlockIsNotAllowed
)
// Map of ErrorCode values back to their constant names for pretty printing.
@@ -217,6 +215,7 @@ var errorCodeStrings = map[ErrorCode]string{
ErrDuplicateBlock: "ErrDuplicateBlock",
ErrBlockMassTooHigh: "ErrBlockMassTooHigh",
ErrBlockVersionTooOld: "ErrBlockVersionTooOld",
ErrInvalidTime: "ErrInvalidTime",
ErrTimeTooOld: "ErrTimeTooOld",
ErrTimeTooNew: "ErrTimeTooNew",
ErrNoParents: "ErrNoParents",
@@ -258,7 +257,6 @@ var errorCodeStrings = map[ErrorCode]string{
ErrInvalidPayloadHash: "ErrInvalidPayloadHash",
ErrInvalidParentsRelation: "ErrInvalidParentsRelation",
ErrDelayedBlockIsNotAllowed: "ErrDelayedBlockIsNotAllowed",
ErrOrphanBlockIsNotAllowed: "ErrOrphanBlockIsNotAllowed",
}
// String returns the ErrorCode as a human-readable name.
@@ -287,7 +285,3 @@ func (e RuleError) Error() string {
func ruleError(c ErrorCode, desc string) error {
return errors.WithStack(RuleError{ErrorCode: c, Description: desc})
}
// ErrInvalidParameter signifies that an invalid parameter has been
// supplied to one of the BlockDAG functions.
var ErrInvalidParameter = errors.New("invalid parameter")

View File

@@ -17,6 +17,7 @@ func TestErrorCodeStringer(t *testing.T) {
{ErrDuplicateBlock, "ErrDuplicateBlock"},
{ErrBlockMassTooHigh, "ErrBlockMassTooHigh"},
{ErrBlockVersionTooOld, "ErrBlockVersionTooOld"},
{ErrInvalidTime, "ErrInvalidTime"},
{ErrTimeTooOld, "ErrTimeTooOld"},
{ErrTimeTooNew, "ErrTimeTooNew"},
{ErrNoParents, "ErrNoParents"},
@@ -57,7 +58,6 @@ func TestErrorCodeStringer(t *testing.T) {
{ErrInvalidPayloadHash, "ErrInvalidPayloadHash"},
{ErrInvalidParentsRelation, "ErrInvalidParentsRelation"},
{ErrDelayedBlockIsNotAllowed, "ErrDelayedBlockIsNotAllowed"},
{ErrOrphanBlockIsNotAllowed, "ErrOrphanBlockIsNotAllowed"},
{0xffff, "Unknown ErrorCode (65535)"},
}

View File

@@ -2,23 +2,22 @@ package blockdag_test
import (
"fmt"
"github.com/pkg/errors"
"math"
"strings"
"testing"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/testtools"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/mining"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/mining"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/wire"
)
// TestFinality checks that the finality mechanism works as expected.
@@ -41,7 +40,7 @@ import (
func TestFinality(t *testing.T) {
params := dagconfig.SimnetParams
params.K = 1
params.FinalityDuration = 100 * params.TargetTimePerBlock
params.FinalityInterval = 100
dag, teardownFunc, err := blockdag.DAGSetup("TestFinality", true, blockdag.Config{
DAGParams: &params,
})
@@ -50,7 +49,7 @@ func TestFinality(t *testing.T) {
}
defer teardownFunc()
buildNodeToDag := func(parentHashes []*daghash.Hash) (*util.Block, error) {
msgBlock, err := mining.PrepareBlockForTest(dag, parentHashes, nil, false)
msgBlock, err := mining.PrepareBlockForTest(dag, &params, parentHashes, nil, false)
if err != nil {
return nil, err
}
@@ -75,7 +74,7 @@ func TestFinality(t *testing.T) {
currentNode := genesis
// First we build a chain of params.FinalityInterval blocks for future use
for i := uint64(0); i < dag.FinalityInterval(); i++ {
for i := uint64(0); i < params.FinalityInterval; i++ {
currentNode, err = buildNodeToDag([]*daghash.Hash{currentNode.Hash()})
if err != nil {
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
@@ -87,7 +86,7 @@ func TestFinality(t *testing.T) {
// Now we build a new chain of 2 * params.FinalityInterval blocks, pointed to genesis, and
// we expect the block with height 1 * params.FinalityInterval to be the last finality point
currentNode = genesis
for i := uint64(0); i < dag.FinalityInterval(); i++ {
for i := uint64(0); i < params.FinalityInterval; i++ {
currentNode, err = buildNodeToDag([]*daghash.Hash{currentNode.Hash()})
if err != nil {
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
@@ -96,7 +95,7 @@ func TestFinality(t *testing.T) {
expectedFinalityPoint := currentNode
for i := uint64(0); i < dag.FinalityInterval(); i++ {
for i := uint64(0); i < params.FinalityInterval; i++ {
currentNode, err = buildNodeToDag([]*daghash.Hash{currentNode.Hash()})
if err != nil {
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
@@ -125,7 +124,7 @@ func TestFinality(t *testing.T) {
t.Errorf("NextBlockCoinbaseTransaction: %s", err)
}
merkleRoot := blockdag.BuildHashMerkleTreeStore([]*util.Tx{fakeCoinbaseTx}).Root()
beforeFinalityBlock := domainmessage.NewMsgBlock(&domainmessage.BlockHeader{
beforeFinalityBlock := wire.NewMsgBlock(&wire.BlockHeader{
Version: 0x10000000,
ParentHashes: []*daghash.Hash{genesis.Hash()},
HashMerkleRoot: merkleRoot,
@@ -164,7 +163,7 @@ func TestFinality(t *testing.T) {
}
// TestFinalityInterval tests that the finality interval is
// smaller then domainmessage.MaxInvPerMsg, so when a peer receives
// smaller then wire.MaxInvPerMsg, so when a peer receives
// a getblocks message it should always be able to send
// all the necessary invs.
func TestFinalityInterval(t *testing.T) {
@@ -176,19 +175,9 @@ func TestFinalityInterval(t *testing.T) {
&dagconfig.SimnetParams,
}
for _, params := range netParams {
func() {
dag, teardownFunc, err := blockdag.DAGSetup("TestFinalityInterval", true, blockdag.Config{
DAGParams: params,
})
if err != nil {
t.Fatalf("Failed to setup dag instance for %s: %v", params.Name, err)
}
defer teardownFunc()
if dag.FinalityInterval() > domainmessage.MaxInvPerMsg {
t.Errorf("FinalityInterval in %s should be lower or equal to domainmessage.MaxInvPerMsg", params.Name)
}
}()
if params.FinalityInterval > wire.MaxInvPerMsg {
t.Errorf("FinalityInterval in %s should be lower or equal to wire.MaxInvPerMsg", params.Name)
}
}
}
@@ -197,7 +186,6 @@ func TestSubnetworkRegistry(t *testing.T) {
params := dagconfig.SimnetParams
params.K = 1
params.BlockCoinbaseMaturity = 0
params.EnableNonNativeSubnetworks = true
dag, teardownFunc, err := blockdag.DAGSetup("TestSubnetworkRegistry", true, blockdag.Config{
DAGParams: &params,
})
@@ -211,7 +199,7 @@ func TestSubnetworkRegistry(t *testing.T) {
if err != nil {
t.Fatalf("could not register network: %s", err)
}
limit, err := dag.GasLimit(subnetworkID)
limit, err := blockdag.GasLimit(subnetworkID)
if err != nil {
t.Fatalf("could not retrieve gas limit: %s", err)
}
@@ -232,7 +220,7 @@ func TestChainedTransactions(t *testing.T) {
}
defer teardownFunc()
block1, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{params.GenesisHash}, nil, false)
block1, err := mining.PrepareBlockForTest(dag, &params, []*daghash.Hash{params.GenesisHash}, nil, false)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -253,34 +241,34 @@ func TestChainedTransactions(t *testing.T) {
if err != nil {
t.Fatalf("Failed to build signature script: %s", err)
}
txIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
txIn := &wire.TxIn{
PreviousOutpoint: wire.Outpoint{TxID: *cbTx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: domainmessage.MaxTxInSequenceNum,
Sequence: wire.MaxTxInSequenceNum,
}
txOut := &domainmessage.TxOut{
txOut := &wire.TxOut{
ScriptPubKey: blockdag.OpTrueScript,
Value: uint64(1),
}
tx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut})
tx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
chainedTxIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *tx.TxID(), Index: 0},
chainedTxIn := &wire.TxIn{
PreviousOutpoint: wire.Outpoint{TxID: *tx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: domainmessage.MaxTxInSequenceNum,
Sequence: wire.MaxTxInSequenceNum,
}
scriptPubKey, err := txscript.PayToScriptHashScript(blockdag.OpTrueScript)
if err != nil {
t.Fatalf("Failed to build public key script: %s", err)
}
chainedTxOut := &domainmessage.TxOut{
chainedTxOut := &wire.TxOut{
ScriptPubKey: scriptPubKey,
Value: uint64(1),
}
chainedTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{chainedTxIn}, []*domainmessage.TxOut{chainedTxOut})
chainedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{chainedTxIn}, []*wire.TxOut{chainedTxOut})
block2, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{block1.BlockHash()}, []*domainmessage.MsgTx{tx}, false)
block2, err := mining.PrepareBlockForTest(dag, &params, []*daghash.Hash{block1.BlockHash()}, []*wire.MsgTx{tx}, false)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -315,18 +303,18 @@ func TestChainedTransactions(t *testing.T) {
t.Errorf("ProcessBlock: block2 got unexpectedly orphaned")
}
nonChainedTxIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
nonChainedTxIn := &wire.TxIn{
PreviousOutpoint: wire.Outpoint{TxID: *cbTx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: domainmessage.MaxTxInSequenceNum,
Sequence: wire.MaxTxInSequenceNum,
}
nonChainedTxOut := &domainmessage.TxOut{
nonChainedTxOut := &wire.TxOut{
ScriptPubKey: scriptPubKey,
Value: uint64(1),
}
nonChainedTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{nonChainedTxIn}, []*domainmessage.TxOut{nonChainedTxOut})
nonChainedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{nonChainedTxIn}, []*wire.TxOut{nonChainedTxOut})
block3, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{block1.BlockHash()}, []*domainmessage.MsgTx{nonChainedTx}, false)
block3, err := mining.PrepareBlockForTest(dag, &params, []*daghash.Hash{block1.BlockHash()}, []*wire.MsgTx{nonChainedTx}, false)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -363,27 +351,27 @@ func TestOrderInDiffFromAcceptanceData(t *testing.T) {
createBlock := func(previousBlock *util.Block) *util.Block {
// Prepare a transaction that spends the previous block's coinbase transaction
var txs []*domainmessage.MsgTx
var txs []*wire.MsgTx
if !previousBlock.IsGenesis() {
previousCoinbaseTx := previousBlock.MsgBlock().Transactions[0]
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
if err != nil {
t.Fatalf("TestOrderInDiffFromAcceptanceData: Failed to build signature script: %s", err)
}
txIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *previousCoinbaseTx.TxID(), Index: 0},
txIn := &wire.TxIn{
PreviousOutpoint: wire.Outpoint{TxID: *previousCoinbaseTx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: domainmessage.MaxTxInSequenceNum,
Sequence: wire.MaxTxInSequenceNum,
}
txOut := &domainmessage.TxOut{
txOut := &wire.TxOut{
ScriptPubKey: blockdag.OpTrueScript,
Value: uint64(1),
}
txs = append(txs, domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut}))
txs = append(txs, wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}))
}
// Create the block
msgBlock, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{previousBlock.Hash()}, txs, false)
msgBlock, err := mining.PrepareBlockForTest(dag, &params, []*daghash.Hash{previousBlock.Hash()}, txs, false)
if err != nil {
t.Fatalf("TestOrderInDiffFromAcceptanceData: Failed to prepare block: %s", err)
}
@@ -422,7 +410,6 @@ func TestGasLimit(t *testing.T) {
params := dagconfig.SimnetParams
params.K = 1
params.BlockCoinbaseMaturity = 0
params.EnableNonNativeSubnetworks = true
dag, teardownFunc, err := blockdag.DAGSetup("TestSubnetworkRegistry", true, blockdag.Config{
DAGParams: &params,
})
@@ -438,9 +425,9 @@ func TestGasLimit(t *testing.T) {
t.Fatalf("could not register network: %s", err)
}
cbTxs := []*domainmessage.MsgTx{}
cbTxs := []*wire.MsgTx{}
for i := 0; i < 4; i++ {
fundsBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), nil, false)
fundsBlock, err := mining.PrepareBlockForTest(dag, &params, dag.TipHashes(), nil, false)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -469,30 +456,30 @@ func TestGasLimit(t *testing.T) {
t.Fatalf("Failed to build public key script: %s", err)
}
tx1In := &domainmessage.TxIn{
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[0].TxID(), 0),
Sequence: domainmessage.MaxTxInSequenceNum,
tx1In := &wire.TxIn{
PreviousOutpoint: *wire.NewOutpoint(cbTxs[0].TxID(), 0),
Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
tx1Out := &domainmessage.TxOut{
tx1Out := &wire.TxOut{
Value: cbTxs[0].TxOut[0].Value,
ScriptPubKey: scriptPubKey,
}
tx1 := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{tx1In}, []*domainmessage.TxOut{tx1Out}, subnetworkID, 10000, []byte{})
tx1 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{tx1In}, []*wire.TxOut{tx1Out}, subnetworkID, 10000, []byte{})
tx2In := &domainmessage.TxIn{
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[1].TxID(), 0),
Sequence: domainmessage.MaxTxInSequenceNum,
tx2In := &wire.TxIn{
PreviousOutpoint: *wire.NewOutpoint(cbTxs[1].TxID(), 0),
Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
tx2Out := &domainmessage.TxOut{
tx2Out := &wire.TxOut{
Value: cbTxs[1].TxOut[0].Value,
ScriptPubKey: scriptPubKey,
}
tx2 := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{tx2In}, []*domainmessage.TxOut{tx2Out}, subnetworkID, 10000, []byte{})
tx2 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{tx2In}, []*wire.TxOut{tx2Out}, subnetworkID, 10000, []byte{})
// Here we check that we can't process a block that has transactions that exceed the gas limit
overLimitBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{tx1, tx2}, true)
overLimitBlock, err := mining.PrepareBlockForTest(dag, &params, dag.TipHashes(), []*wire.MsgTx{tx1, tx2}, true)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -514,20 +501,20 @@ func TestGasLimit(t *testing.T) {
t.Fatalf("ProcessBlock: overLimitBlock got unexpectedly orphan")
}
overflowGasTxIn := &domainmessage.TxIn{
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[2].TxID(), 0),
Sequence: domainmessage.MaxTxInSequenceNum,
overflowGasTxIn := &wire.TxIn{
PreviousOutpoint: *wire.NewOutpoint(cbTxs[2].TxID(), 0),
Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
overflowGasTxOut := &domainmessage.TxOut{
overflowGasTxOut := &wire.TxOut{
Value: cbTxs[2].TxOut[0].Value,
ScriptPubKey: scriptPubKey,
}
overflowGasTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{overflowGasTxIn}, []*domainmessage.TxOut{overflowGasTxOut},
overflowGasTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{overflowGasTxIn}, []*wire.TxOut{overflowGasTxOut},
subnetworkID, math.MaxUint64, []byte{})
// Here we check that we can't process a block that its transactions' gas overflows uint64
overflowGasBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{tx1, overflowGasTx}, true)
overflowGasBlock, err := mining.PrepareBlockForTest(dag, &params, dag.TipHashes(), []*wire.MsgTx{tx1, overflowGasTx}, true)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -549,19 +536,19 @@ func TestGasLimit(t *testing.T) {
}
nonExistentSubnetwork := &subnetworkid.SubnetworkID{123}
nonExistentSubnetworkTxIn := &domainmessage.TxIn{
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[3].TxID(), 0),
Sequence: domainmessage.MaxTxInSequenceNum,
nonExistentSubnetworkTxIn := &wire.TxIn{
PreviousOutpoint: *wire.NewOutpoint(cbTxs[3].TxID(), 0),
Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
nonExistentSubnetworkTxOut := &domainmessage.TxOut{
nonExistentSubnetworkTxOut := &wire.TxOut{
Value: cbTxs[3].TxOut[0].Value,
ScriptPubKey: scriptPubKey,
}
nonExistentSubnetworkTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{nonExistentSubnetworkTxIn},
[]*domainmessage.TxOut{nonExistentSubnetworkTxOut}, nonExistentSubnetwork, 1, []byte{})
nonExistentSubnetworkTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{nonExistentSubnetworkTxIn},
[]*wire.TxOut{nonExistentSubnetworkTxOut}, nonExistentSubnetwork, 1, []byte{})
nonExistentSubnetworkBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{nonExistentSubnetworkTx, overflowGasTx}, true)
nonExistentSubnetworkBlock, err := mining.PrepareBlockForTest(dag, &params, dag.TipHashes(), []*wire.MsgTx{nonExistentSubnetworkTx, overflowGasTx}, true)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -582,7 +569,7 @@ func TestGasLimit(t *testing.T) {
}
// Here we check that we can process a block with a transaction that doesn't exceed the gas limit
validBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{tx1}, true)
validBlock, err := mining.PrepareBlockForTest(dag, &params, dag.TipHashes(), []*wire.MsgTx{tx1}, true)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}

View File

@@ -1,7 +1,7 @@
package blockdag
import (
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/pkg/errors"
"sort"
)
@@ -57,7 +57,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
// newNode is always in the future of blueCandidate, so there's
// no point in checking it.
if chainBlock != newNode {
if isAncestorOfBlueCandidate, err := dag.isInPast(chainBlock, blueCandidate); err != nil {
if isAncestorOfBlueCandidate, err := dag.isAncestorOf(chainBlock, blueCandidate); err != nil {
return nil, err
} else if isAncestorOfBlueCandidate {
break
@@ -66,7 +66,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
for _, block := range chainBlock.blues {
// Skip blocks that exist in the past of blueCandidate.
if isAncestorOfBlueCandidate, err := dag.isInPast(block, blueCandidate); err != nil {
if isAncestorOfBlueCandidate, err := dag.isAncestorOf(block, blueCandidate); err != nil {
return nil, err
} else if isAncestorOfBlueCandidate {
continue
@@ -78,13 +78,13 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
}
candidateAnticoneSize++
if candidateAnticoneSize > dag.Params.K {
if candidateAnticoneSize > dag.dagParams.K {
// k-cluster violation: The candidate's blue anticone exceeded k
possiblyBlue = false
break
}
if candidateBluesAnticoneSizes[block] == dag.Params.K {
if candidateBluesAnticoneSizes[block] == dag.dagParams.K {
// k-cluster violation: A block in candidate's blue anticone already
// has k blue blocks in its own anticone
possiblyBlue = false
@@ -93,7 +93,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
// This is a sanity check that validates that a blue
// block's blue anticone is not already larger than K.
if candidateBluesAnticoneSizes[block] > dag.Params.K {
if candidateBluesAnticoneSizes[block] > dag.dagParams.K {
return nil, errors.New("found blue anticone size larger than k")
}
}
@@ -109,7 +109,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
// The maximum length of node.blues can be K+1 because
// it contains the selected parent.
if dagconfig.KType(len(newNode.blues)) == dag.Params.K+1 {
if dagconfig.KType(len(newNode.blues)) == dag.dagParams.K+1 {
break
}
}
@@ -148,7 +148,7 @@ func (dag *BlockDAG) selectedParentAnticone(node *blockNode) ([]*blockNode, erro
if anticoneSet.contains(parent) || selectedParentPast.contains(parent) {
continue
}
isAncestorOfSelectedParent, err := dag.isInPast(parent, node.selectedParent)
isAncestorOfSelectedParent, err := dag.isAncestorOf(parent, node.selectedParent)
if err != nil {
return nil, err
}

View File

@@ -2,15 +2,14 @@ package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"reflect"
"sort"
"strings"
"testing"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
)
type testBlockData struct {
@@ -34,7 +33,7 @@ func TestGHOSTDAG(t *testing.T) {
}{
{
k: 3,
expectedReds: []string{"F", "G", "H", "I", "O", "P"},
expectedReds: []string{"F", "G", "H", "I", "N", "O"},
dagData: []*testBlockData{
{
parents: []string{"A"},
@@ -167,7 +166,7 @@ func TestGHOSTDAG(t *testing.T) {
id: "T",
expectedScore: 13,
expectedSelectedParent: "S",
expectedBlues: []string{"S", "Q", "N"},
expectedBlues: []string{"S", "P", "Q"},
},
},
},
@@ -216,10 +215,7 @@ func TestGHOSTDAG(t *testing.T) {
t.Fatalf("TestGHOSTDAG: block %v was unexpectedly orphan", blockData.id)
}
node, ok := dag.index.LookupNode(utilBlock.Hash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", utilBlock.Hash())
}
node := dag.index.LookupNode(utilBlock.Hash())
blockByIDMap[blockData.id] = node
idByBlockMap[node] = blockData.id
@@ -295,29 +291,22 @@ func TestBlueAnticoneSizeErrors(t *testing.T) {
defer teardownFunc()
// Prepare a block chain with size K beginning with the genesis block
currentBlockA := dag.Params.GenesisBlock
for i := dagconfig.KType(0); i < dag.Params.K; i++ {
currentBlockA := dag.dagParams.GenesisBlock
for i := dagconfig.KType(0); i < dag.dagParams.K; i++ {
newBlock := prepareAndProcessBlockByParentMsgBlocks(t, dag, currentBlockA)
currentBlockA = newBlock
}
// Prepare another block chain with size K beginning with the genesis block
currentBlockB := dag.Params.GenesisBlock
for i := dagconfig.KType(0); i < dag.Params.K; i++ {
currentBlockB := dag.dagParams.GenesisBlock
for i := dagconfig.KType(0); i < dag.dagParams.K; i++ {
newBlock := prepareAndProcessBlockByParentMsgBlocks(t, dag, currentBlockB)
currentBlockB = newBlock
}
// Get references to the tips of the two chains
blockNodeA, ok := dag.index.LookupNode(currentBlockA.BlockHash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", currentBlockA.BlockHash())
}
blockNodeB, ok := dag.index.LookupNode(currentBlockB.BlockHash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", currentBlockB.BlockHash())
}
blockNodeA := dag.index.LookupNode(currentBlockA.BlockHash())
blockNodeB := dag.index.LookupNode(currentBlockB.BlockHash())
// Try getting the blueAnticoneSize between them. Since the two
// blocks are not in the anticones of eachother, this should fail.
@@ -343,16 +332,16 @@ func TestGHOSTDAGErrors(t *testing.T) {
defer teardownFunc()
// Add two child blocks to the genesis
block1 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.Params.GenesisBlock)
block2 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.Params.GenesisBlock)
block1 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
block2 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
// Add a child block to the previous two blocks
block3 := prepareAndProcessBlockByParentMsgBlocks(t, dag, block1, block2)
// Clear the reachability store
dag.reachabilityTree.store.loaded = map[daghash.Hash]*reachabilityData{}
dag.reachabilityStore.loaded = map[daghash.Hash]*reachabilityData{}
dbTx, err := dag.databaseContext.NewTx()
dbTx, err := dbaccess.NewTx()
if err != nil {
t.Fatalf("NewTx: %s", err)
}
@@ -370,15 +359,12 @@ func TestGHOSTDAGErrors(t *testing.T) {
// Try to rerun GHOSTDAG on the last block. GHOSTDAG uses
// reachability data, so we expect it to fail.
blockNode3, ok := dag.index.LookupNode(block3.BlockHash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", block3.BlockHash())
}
blockNode3 := dag.index.LookupNode(block3.BlockHash())
_, err = dag.ghostdag(blockNode3)
if err == nil {
t.Fatalf("TestGHOSTDAGErrors: ghostdag unexpectedly succeeded")
}
expectedErrSubstring := "couldn't find reachability data"
expectedErrSubstring := "Couldn't find reachability data"
if !strings.Contains(err.Error(), expectedErrSubstring) {
t.Fatalf("TestGHOSTDAGErrors: ghostdag returned wrong error. "+
"Want: %s, got: %s", expectedErrSubstring, err)

View File

@@ -3,20 +3,18 @@ package indexers
import (
"bytes"
"encoding/gob"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
)
// AcceptanceIndex implements a txAcceptanceData by block hash index. That is to say,
// it stores a mapping between a block's hash and the set of transactions that the
// block accepts among its blue blocks.
type AcceptanceIndex struct {
dag *blockdag.BlockDAG
databaseContext *dbaccess.DatabaseContext
dag *blockdag.BlockDAG
}
// Ensure the AcceptanceIndex type implements the Indexer interface.
@@ -33,8 +31,8 @@ func NewAcceptanceIndex() *AcceptanceIndex {
}
// DropAcceptanceIndex drops the acceptance index.
func DropAcceptanceIndex(databaseContext *dbaccess.DatabaseContext) error {
dbTx, err := databaseContext.NewTx()
func DropAcceptanceIndex() error {
dbTx, err := dbaccess.NewTx()
if err != nil {
return err
}
@@ -51,9 +49,8 @@ func DropAcceptanceIndex(databaseContext *dbaccess.DatabaseContext) error {
// Init initializes the hash-based acceptance index.
//
// This is part of the Indexer interface.
func (idx *AcceptanceIndex) Init(dag *blockdag.BlockDAG, databaseContext *dbaccess.DatabaseContext) error {
func (idx *AcceptanceIndex) Init(dag *blockdag.BlockDAG) error {
idx.dag = dag
idx.databaseContext = databaseContext
return idx.recover()
}
@@ -62,13 +59,13 @@ func (idx *AcceptanceIndex) Init(dag *blockdag.BlockDAG, databaseContext *dbacce
//
// This is part of the Indexer interface.
func (idx *AcceptanceIndex) recover() error {
return idx.dag.ForEachHash(func(hash daghash.Hash) error {
dbTx, err := idx.databaseContext.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
dbTx, err := dbaccess.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
err = idx.dag.ForEachHash(func(hash daghash.Hash) error {
exists, err := dbaccess.HasAcceptanceData(dbTx, &hash)
if err != nil {
return err
@@ -80,13 +77,13 @@ func (idx *AcceptanceIndex) recover() error {
if err != nil {
return err
}
err = idx.ConnectBlock(dbTx, &hash, txAcceptanceData)
if err != nil {
return err
}
return dbTx.Commit()
return idx.ConnectBlock(dbTx, &hash, txAcceptanceData)
})
if err != nil {
return err
}
return dbTx.Commit()
}
// ConnectBlock is invoked by the index manager when a new block has been
@@ -105,7 +102,7 @@ func (idx *AcceptanceIndex) ConnectBlock(dbContext *dbaccess.TxContext, blockHas
// TxsAcceptanceData returns the acceptance data of all the transactions that
// were accepted by the block with hash blockHash.
func (idx *AcceptanceIndex) TxsAcceptanceData(blockHash *daghash.Hash) (blockdag.MultiBlockTxsAcceptanceData, error) {
serializedTxsAcceptanceData, err := dbaccess.FetchAcceptanceData(idx.databaseContext, blockHash)
serializedTxsAcceptanceData, err := dbaccess.FetchAcceptanceData(dbaccess.NoTx(), blockHash)
if err != nil {
return nil, err
}
@@ -113,7 +110,7 @@ func (idx *AcceptanceIndex) TxsAcceptanceData(blockHash *daghash.Hash) (blockdag
}
type serializableTxAcceptanceData struct {
MsgTx domainmessage.MsgTx
MsgTx wire.MsgTx
IsAccepted bool
}

View File

@@ -1,6 +1,13 @@
package indexers
import (
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
"github.com/pkg/errors"
"io"
"io/ioutil"
"os"
@@ -8,32 +15,24 @@ import (
"reflect"
"syscall"
"testing"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
func TestAcceptanceIndexSerializationAndDeserialization(t *testing.T) {
// Create test data
hash, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")
txIn1 := &domainmessage.TxIn{SignatureScript: []byte{1}, PreviousOutpoint: domainmessage.Outpoint{Index: 1}, Sequence: 0}
txIn2 := &domainmessage.TxIn{SignatureScript: []byte{2}, PreviousOutpoint: domainmessage.Outpoint{Index: 2}, Sequence: 0}
txOut1 := &domainmessage.TxOut{ScriptPubKey: []byte{1}, Value: 10}
txOut2 := &domainmessage.TxOut{ScriptPubKey: []byte{2}, Value: 20}
txIn1 := &wire.TxIn{SignatureScript: []byte{1}, PreviousOutpoint: wire.Outpoint{Index: 1}, Sequence: 0}
txIn2 := &wire.TxIn{SignatureScript: []byte{2}, PreviousOutpoint: wire.Outpoint{Index: 2}, Sequence: 0}
txOut1 := &wire.TxOut{ScriptPubKey: []byte{1}, Value: 10}
txOut2 := &wire.TxOut{ScriptPubKey: []byte{2}, Value: 20}
blockTxsAcceptanceData := blockdag.BlockTxsAcceptanceData{
BlockHash: *hash,
TxAcceptanceData: []blockdag.TxAcceptanceData{
{
Tx: util.NewTx(domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn1}, []*domainmessage.TxOut{txOut1})),
Tx: util.NewTx(wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn1}, []*wire.TxOut{txOut1})),
IsAccepted: true,
},
{
Tx: util.NewTx(domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn2}, []*domainmessage.TxOut{txOut2})),
Tx: util.NewTx(wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn2}, []*wire.TxOut{txOut2})),
IsAccepted: false,
},
},
@@ -97,15 +96,14 @@ func TestAcceptanceIndexRecover(t *testing.T) {
}
defer os.RemoveAll(db1Path)
databaseContext1, err := dbaccess.New(db1Path)
err = dbaccess.Open(db1Path)
if err != nil {
t.Fatalf("error creating db: %s", err)
}
db1Config := blockdag.Config{
IndexManager: db1IndexManager,
DAGParams: params,
DatabaseContext: databaseContext1,
IndexManager: db1IndexManager,
DAGParams: params,
}
db1DAG, teardown, err := blockdag.DAGSetup("", false, db1Config)
@@ -162,18 +160,17 @@ func TestAcceptanceIndexRecover(t *testing.T) {
t.Fatalf("Error fetching acceptance data: %s", err)
}
err = databaseContext1.Close()
err = dbaccess.Close()
if err != nil {
t.Fatalf("Error closing the database: %s", err)
}
databaseContext2, err := dbaccess.New(db2Path)
err = dbaccess.Open(db2Path)
if err != nil {
t.Fatalf("error creating db: %s", err)
}
db2Config := blockdag.Config{
DAGParams: params,
DatabaseContext: databaseContext2,
DAGParams: params,
}
db2DAG, teardown, err := blockdag.DAGSetup("", false, db2Config)
@@ -209,11 +206,11 @@ func TestAcceptanceIndexRecover(t *testing.T) {
t.Fatalf("copyDirectory: %s", err)
}
err = databaseContext2.Close()
err = dbaccess.Close()
if err != nil {
t.Fatalf("Error closing the database: %s", err)
}
databaseContext3, err := dbaccess.New(db3Path)
err = dbaccess.Open(db3Path)
if err != nil {
t.Fatalf("error creating db: %s", err)
}
@@ -221,9 +218,8 @@ func TestAcceptanceIndexRecover(t *testing.T) {
db3AcceptanceIndex := NewAcceptanceIndex()
db3IndexManager := NewManager([]Indexer{db3AcceptanceIndex})
db3Config := blockdag.Config{
IndexManager: db3IndexManager,
DAGParams: params,
DatabaseContext: databaseContext3,
IndexManager: db3IndexManager,
DAGParams: params,
}
_, teardown, err = blockdag.DAGSetup("", false, db3Config)

View File

@@ -8,8 +8,8 @@ Package indexers implements optional block DAG indexes.
package indexers
import (
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util/daghash"
)
@@ -18,7 +18,7 @@ import (
type Indexer interface {
// Init is invoked when the index manager is first initializing the
// index.
Init(dag *blockdag.BlockDAG, databaseContext *dbaccess.DatabaseContext) error
Init(dag *blockdag.BlockDAG) error
// ConnectBlock is invoked when the index manager is notified that a new
// block has been connected to the DAG.

View File

@@ -5,7 +5,7 @@
package indexers
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/logger"
)
var log, _ = logger.Get(logger.SubsystemTags.INDX)

View File

@@ -5,8 +5,8 @@
package indexers
import (
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util/daghash"
)
@@ -22,9 +22,9 @@ var _ blockdag.IndexManager = (*Manager)(nil)
// Init initializes the enabled indexes.
// This is part of the blockdag.IndexManager interface.
func (m *Manager) Init(dag *blockdag.BlockDAG, databaseContext *dbaccess.DatabaseContext) error {
func (m *Manager) Init(dag *blockdag.BlockDAG) error {
for _, indexer := range m.enabledIndexes {
if err := indexer.Init(dag, databaseContext); err != nil {
if err := indexer.Init(dag); err != nil {
return err
}
}

View File

@@ -5,7 +5,7 @@
package blockdag
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/logger"
"github.com/kaspanet/kaspad/util/panics"
)

View File

@@ -5,9 +5,7 @@
package blockdag
import (
"fmt"
"math"
"sort"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
@@ -134,49 +132,3 @@ func buildMerkleTreeStore(hashes []*daghash.Hash) MerkleTree {
return merkles
}
func calculateAcceptedIDMerkleRoot(multiBlockTxsAcceptanceData MultiBlockTxsAcceptanceData) *daghash.Hash {
var acceptedTxs []*util.Tx
for _, blockTxsAcceptanceData := range multiBlockTxsAcceptanceData {
for _, txAcceptance := range blockTxsAcceptanceData.TxAcceptanceData {
if !txAcceptance.IsAccepted {
continue
}
acceptedTxs = append(acceptedTxs, txAcceptance.Tx)
}
}
sort.Slice(acceptedTxs, func(i, j int) bool {
return daghash.LessTxID(acceptedTxs[i].ID(), acceptedTxs[j].ID())
})
acceptedIDMerkleTree := BuildIDMerkleTreeStore(acceptedTxs)
return acceptedIDMerkleTree.Root()
}
func (node *blockNode) validateAcceptedIDMerkleRoot(dag *BlockDAG, txsAcceptanceData MultiBlockTxsAcceptanceData) error {
if node.isGenesis() {
return nil
}
calculatedAccepetedIDMerkleRoot := calculateAcceptedIDMerkleRoot(txsAcceptanceData)
header := node.Header()
if !header.AcceptedIDMerkleRoot.IsEqual(calculatedAccepetedIDMerkleRoot) {
str := fmt.Sprintf("block accepted ID merkle root is invalid - block "+
"header indicates %s, but calculated value is %s",
header.AcceptedIDMerkleRoot, calculatedAccepetedIDMerkleRoot)
return ruleError(ErrBadMerkleRoot, str)
}
return nil
}
// NextAcceptedIDMerkleRootNoLock prepares the acceptedIDMerkleRoot for the next mined block
//
// This function MUST be called with the DAG read-lock held
func (dag *BlockDAG) NextAcceptedIDMerkleRootNoLock() (*daghash.Hash, error) {
txsAcceptanceData, err := dag.TxsAcceptedByVirtual()
if err != nil {
return nil, err
}
return calculateAcceptedIDMerkleRoot(txsAcceptanceData), nil
}

View File

@@ -3,36 +3,37 @@ package blockdag
import (
"bytes"
"encoding/binary"
"math"
"time"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/wire"
"time"
)
// The current block version
const blockVersion = 0x10000000
// BlockForMining returns a block with the given transactions
// that points to the current DAG tips, that is valid from
// all aspects except proof of work.
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*domainmessage.MsgBlock, error) {
func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*wire.MsgBlock, error) {
blockTimestamp := dag.NextBlockTime()
requiredDifficulty := dag.NextRequiredDifficulty(blockTimestamp)
// Calculate the next expected block version based on the state of the
// rule change deployments.
nextBlockVersion, err := dag.CalcNextBlockVersion()
if err != nil {
return nil, err
}
// Create a new block ready to be solved.
hashMerkleTree := BuildHashMerkleTreeStore(transactions)
acceptedIDMerkleRoot, err := dag.NextAcceptedIDMerkleRootNoLock()
if err != nil {
return nil, err
}
var msgBlock domainmessage.MsgBlock
var msgBlock wire.MsgBlock
for _, tx := range transactions {
msgBlock.AddTransaction(tx.MsgTx())
}
@@ -42,8 +43,8 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*domainmessage.Msg
return nil, err
}
msgBlock.Header = domainmessage.BlockHeader{
Version: blockVersion,
msgBlock.Header = wire.BlockHeader{
Version: nextBlockVersion,
ParentHashes: dag.TipHashes(),
HashMerkleRoot: hashMerkleTree.Root(),
AcceptedIDMerkleRoot: acceptedIDMerkleRoot,
@@ -103,19 +104,19 @@ func (dag *BlockDAG) NextCoinbaseFromAddress(payToAddress util.Address, extraDat
// on the end of the DAG. In particular, it is one second after
// the median timestamp of the last several blocks per the DAG consensus
// rules.
func (dag *BlockDAG) NextBlockMinimumTime() mstime.Time {
return dag.CalcPastMedianTime().Add(time.Millisecond)
func (dag *BlockDAG) NextBlockMinimumTime() time.Time {
return dag.CalcPastMedianTime().Add(time.Second)
}
// NextBlockTime returns a valid block time for the
// next block that will point to the existing DAG tips.
func (dag *BlockDAG) NextBlockTime() mstime.Time {
func (dag *BlockDAG) NextBlockTime() time.Time {
// The timestamp for the block must not be before the median timestamp
// of the last several blocks. Thus, choose the maximum between the
// current time and one second after the past median time. The current
// timestamp is truncated to a millisecond boundary before comparison since a
// timestamp is truncated to a second boundary before comparison since a
// block timestamp does not supported a precision greater than one
// millisecond.
// second.
newTimestamp := dag.Now()
minTimestamp := dag.NextBlockMinimumTime()
if newTimestamp.Before(minTimestamp) {
@@ -124,15 +125,3 @@ func (dag *BlockDAG) NextBlockTime() mstime.Time {
return newTimestamp
}
// CurrentBits returns the bits of the tip with the lowest bits, which also means it has highest difficulty.
func (dag *BlockDAG) CurrentBits() uint32 {
tips := dag.virtual.tips()
minBits := uint32(math.MaxUint32)
for tip := range tips {
if minBits > tip.Header().Bits {
minBits = tip.Header().Bits
}
}
return minBits
}

View File

@@ -3,7 +3,7 @@ package blockdag
import (
"bytes"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/locks"
"github.com/pkg/errors"

View File

@@ -8,7 +8,7 @@ import (
"path/filepath"
"testing"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
)
// TestNotifications ensures that notification callbacks are fired on events.

277
blockdag/process.go Normal file
View File

@@ -0,0 +1,277 @@
// 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/dagconfig"
"github.com/pkg/errors"
"time"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
)
// BehaviorFlags is a bitmask defining tweaks to the normal behavior when
// performing DAG processing and consensus rules checks.
type BehaviorFlags uint32
const (
// BFFastAdd may be set to indicate that several checks can be avoided
// for the block since it is already known to fit into the DAG due to
// already proving it correct links into the DAG.
BFFastAdd BehaviorFlags = 1 << iota
// BFNoPoWCheck may be set to indicate the proof of work check which
// ensures a block hashes to a value less than the required target will
// not be performed.
BFNoPoWCheck
// BFWasUnorphaned may be set to indicate that a block was just now
// unorphaned
BFWasUnorphaned
// BFAfterDelay may be set to indicate that a block had timestamp too far
// in the future, just finished the delay
BFAfterDelay
// BFIsSync may be set to indicate that the block was sent as part of the
// netsync process
BFIsSync
// BFWasStored is set to indicate that the block was previously stored
// in the block index but was never fully processed
BFWasStored
// BFDisallowDelay is set to indicate that a delayed block should be rejected.
// This is used for the case where a block is submitted through RPC.
BFDisallowDelay
// BFNone is a convenience value to specifically indicate no flags.
BFNone BehaviorFlags = 0
)
// IsInDAG determines whether a block with the given hash exists in
// the DAG.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) IsInDAG(hash *daghash.Hash) bool {
return dag.index.HaveBlock(hash)
}
// processOrphans determines if there are any orphans which depend on the passed
// block hash (they are no longer orphans if true) and potentially accepts them.
// It repeats the process for the newly accepted blocks (to detect further
// orphans which may no longer be orphans) until there are no more.
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to maybeAcceptBlock.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) processOrphans(hash *daghash.Hash, flags BehaviorFlags) error {
// Start with processing at least the passed hash. Leave a little room
// for additional orphan blocks that need to be processed without
// needing to grow the array in the common case.
processHashes := make([]*daghash.Hash, 0, 10)
processHashes = append(processHashes, hash)
for len(processHashes) > 0 {
// Pop the first hash to process from the slice.
processHash := processHashes[0]
processHashes[0] = nil // Prevent GC leak.
processHashes = processHashes[1:]
// Look up all orphans that are parented by the block we just
// accepted. An indexing for loop is
// intentionally used over a range here as range does not
// reevaluate the slice on each iteration nor does it adjust the
// index for the modified slice.
for i := 0; i < len(dag.prevOrphans[*processHash]); i++ {
orphan := dag.prevOrphans[*processHash][i]
if orphan == nil {
log.Warnf("Found a nil entry at index %d in the "+
"orphan dependency list for block %s", i,
processHash)
continue
}
// Skip this orphan if one or more of its parents are
// still missing.
_, err := lookupParentNodes(orphan.block, dag)
if err != nil {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrParentBlockUnknown {
continue
}
return err
}
// Remove the orphan from the orphan pool.
orphanHash := orphan.block.Hash()
dag.removeOrphanBlock(orphan)
i--
// Potentially accept the block into the block DAG.
err = dag.maybeAcceptBlock(orphan.block, flags|BFWasUnorphaned)
if err != nil {
// Since we don't want to reject the original block because of
// a bad unorphaned child, only return an error if it's not a RuleError.
if !errors.As(err, &RuleError{}) {
return err
}
log.Warnf("Verification failed for orphan block %s: %s", orphanHash, err)
}
// Add this block to the list of blocks to process so
// any orphan blocks that depend on this block are
// handled too.
processHashes = append(processHashes, orphanHash)
}
}
return nil
}
// ProcessBlock is the main workhorse for handling insertion of new blocks into
// the block DAG. It includes functionality such as rejecting duplicate
// blocks, ensuring blocks follow all rules, orphan handling, and insertion into
// the block DAG.
//
// When no errors occurred during processing, the first return value indicates
// whether or not the block is an orphan.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) ProcessBlock(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
dag.dagLock.Lock()
defer dag.dagLock.Unlock()
return dag.processBlockNoLock(block, flags)
}
func (dag *BlockDAG) processBlockNoLock(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
isAfterDelay := flags&BFAfterDelay == BFAfterDelay
wasBlockStored := flags&BFWasStored == BFWasStored
disallowDelay := flags&BFDisallowDelay == BFDisallowDelay
blockHash := block.Hash()
log.Tracef("Processing block %s", blockHash)
// The block must not already exist in the DAG.
if dag.IsInDAG(blockHash) && !wasBlockStored {
str := fmt.Sprintf("already have block %s", blockHash)
return false, false, ruleError(ErrDuplicateBlock, str)
}
// The block must not already exist as an orphan.
if _, exists := dag.orphans[*blockHash]; exists {
str := fmt.Sprintf("already have block (orphan) %s", blockHash)
return false, false, ruleError(ErrDuplicateBlock, str)
}
if dag.isKnownDelayedBlock(blockHash) {
str := fmt.Sprintf("already have block (delayed) %s", blockHash)
return false, false, ruleError(ErrDuplicateBlock, str)
}
if !isAfterDelay {
// Perform preliminary sanity checks on the block and its transactions.
delay, err := dag.checkBlockSanity(block, flags)
if err != nil {
return false, false, err
}
if delay != 0 && disallowDelay {
str := fmt.Sprintf("Cannot process blocks beyond the allowed time offset while the BFDisallowDelay flag is raised %s", blockHash)
return false, true, ruleError(ErrDelayedBlockIsNotAllowed, str)
}
if delay != 0 {
err = dag.addDelayedBlock(block, delay)
if err != nil {
return false, false, err
}
return false, true, nil
}
}
var missingParents []*daghash.Hash
for _, parentHash := range block.MsgBlock().Header.ParentHashes {
if !dag.IsInDAG(parentHash) {
missingParents = append(missingParents, parentHash)
}
}
// Handle the case of a block with a valid timestamp(non-delayed) which points to a delayed block.
delay, isParentDelayed := dag.maxDelayOfParents(missingParents)
if isParentDelayed {
// Add Nanosecond to ensure that parent process time will be after its child.
delay += time.Nanosecond
err := dag.addDelayedBlock(block, delay)
if err != nil {
return false, false, err
}
return false, true, err
}
// Handle orphan blocks.
if len(missingParents) > 0 {
// Some orphans during netsync are a normal part of the process, since the anticone
// of the chain-split is never explicitly requested.
// Therefore, if we are during netsync - don't report orphans to default logs.
//
// The number K*2 was chosen since in peace times anticone is limited to K blocks,
// while some red block can make it a bit bigger, but much more than that indicates
// there might be some problem with the netsync process.
if flags&BFIsSync == BFIsSync && dagconfig.KType(len(dag.orphans)) < dag.dagParams.K*2 {
log.Debugf("Adding orphan block %s. This is normal part of netsync process", blockHash)
} else {
log.Infof("Adding orphan block %s", blockHash)
}
dag.addOrphanBlock(block)
return true, false, nil
}
// The block has passed all context independent checks and appears sane
// enough to potentially accept it into the block DAG.
err = dag.maybeAcceptBlock(block, flags)
if err != nil {
return false, false, err
}
// Accept any orphan blocks that depend on this block (they are
// no longer orphans) and repeat for those accepted blocks until
// there are no more.
err = dag.processOrphans(blockHash, flags)
if err != nil {
return false, false, err
}
if !isAfterDelay {
err = dag.processDelayedBlocks()
if err != nil {
return false, false, err
}
}
dag.addBlockProcessingTimestamp()
log.Debugf("Accepted block %s", blockHash)
return false, false, nil
}
// maxDelayOfParents returns the maximum delay of the given block hashes.
// Note that delay could be 0, but isDelayed will return true. This is the case where the parent process time is due.
func (dag *BlockDAG) maxDelayOfParents(parentHashes []*daghash.Hash) (delay time.Duration, isDelayed bool) {
for _, parentHash := range parentHashes {
if delayedParent, exists := dag.delayedBlocks[*parentHash]; exists {
isDelayed = true
parentDelay := delayedParent.processTime.Sub(dag.Now())
if parentDelay > delay {
delay = parentDelay
}
}
}
return delay, isDelayed
}

View File

@@ -1,14 +1,12 @@
package blockdag
import (
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/util"
"path/filepath"
"testing"
"time"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util/daghash"
)
@@ -65,8 +63,8 @@ func TestProcessOrphans(t *testing.T) {
}
// Make sure that the child block had been rejected
node, ok := dag.index.LookupNode(childBlock.Hash())
if !ok {
node := dag.index.LookupNode(childBlock.Hash())
if node == nil {
t.Fatalf("TestProcessOrphans: child block missing from block index")
}
if !dag.index.NodeStatus(node).KnownInvalid() {
@@ -91,23 +89,20 @@ func TestProcessDelayedBlocks(t *testing.T) {
}
}()
initialTime := dag1.Params.GenesisBlock.Header.Timestamp
initialTime := dag1.dagParams.GenesisBlock.Header.Timestamp
// Here we use a fake time source that returns a timestamp
// one hour into the future to make delayedBlock artificially
// valid.
dag1.timeSource = newFakeTimeSource(initialTime.Add(time.Hour))
delayedBlock, err := PrepareBlockForTest(dag1, []*daghash.Hash{dag1.Params.GenesisBlock.BlockHash()}, nil)
delayedBlock, err := PrepareBlockForTest(dag1, []*daghash.Hash{dag1.dagParams.GenesisBlock.BlockHash()}, nil)
if err != nil {
t.Fatalf("error in PrepareBlockForTest: %s", err)
}
blockDelay := time.Duration(dag1.Params.TimestampDeviationTolerance)*dag1.Params.TargetTimePerBlock + 5*time.Second
blockDelay := time.Duration(dag1.dagParams.TimestampDeviationTolerance*uint64(dag1.targetTimePerBlock)+5) * time.Second
delayedBlock.Header.Timestamp = initialTime.Add(blockDelay)
// We change the nonce here because processDelayedBlocks always runs without BFNoPoWCheck.
delayedBlock.Header.Nonce = 2
isOrphan, isDelayed, err := dag1.ProcessBlock(util.NewBlock(delayedBlock), BFNoPoWCheck)
if err != nil {
t.Fatalf("ProcessBlock returned unexpected error: %s\n", err)
@@ -182,7 +177,7 @@ func TestProcessDelayedBlocks(t *testing.T) {
t.Errorf("dag.IsKnownBlock should return true for a child of a delayed block")
}
blockBeforeDelay, err := PrepareBlockForTest(dag2, []*daghash.Hash{dag2.Params.GenesisBlock.BlockHash()}, nil)
blockBeforeDelay, err := PrepareBlockForTest(dag2, []*daghash.Hash{dag2.dagParams.GenesisBlock.BlockHash()}, nil)
if err != nil {
t.Fatalf("error in PrepareBlockForTest: %s", err)
}
@@ -207,15 +202,12 @@ func TestProcessDelayedBlocks(t *testing.T) {
}
// We advance the clock to the point where delayedBlock timestamp is valid.
deviationTolerance := time.Duration(dag2.TimestampDeviationTolerance) * dag2.Params.TargetTimePerBlock
timeUntilDelayedBlockIsValid := delayedBlock.Header.Timestamp.
Add(-deviationTolerance).
Sub(dag2.Now()) +
time.Second
dag2.timeSource = newFakeTimeSource(initialTime.Add(timeUntilDelayedBlockIsValid))
deviationTolerance := int64(dag2.TimestampDeviationTolerance) * dag2.targetTimePerBlock
secondsUntilDelayedBlockIsValid := delayedBlock.Header.Timestamp.Unix() - deviationTolerance - dag2.Now().Unix() + 1
dag2.timeSource = newFakeTimeSource(initialTime.Add(time.Duration(secondsUntilDelayedBlockIsValid) * time.Second))
blockAfterDelay, err := PrepareBlockForTest(dag2,
[]*daghash.Hash{dag2.Params.GenesisBlock.BlockHash()},
[]*daghash.Hash{dag2.dagParams.GenesisBlock.BlockHash()},
nil)
if err != nil {
t.Fatalf("error in PrepareBlockForTest: %s", err)
@@ -240,101 +232,3 @@ func TestProcessDelayedBlocks(t *testing.T) {
t.Errorf("delayedBlockChild shouldn't be added to the DAG because its parent has been added to the DAG")
}
}
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, ok := dag.index.LookupNode(block1.Hash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", 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
}

577
blockdag/reachability.go Normal file
View File

@@ -0,0 +1,577 @@
package blockdag
import (
"fmt"
"github.com/pkg/errors"
"math"
"strings"
"time"
)
// reachabilityInterval represents an interval to be used within the
// tree reachability algorithm. See reachabilityTreeNode for further
// details.
type reachabilityInterval struct {
start uint64
end uint64
}
func newReachabilityInterval(start uint64, end uint64) *reachabilityInterval {
return &reachabilityInterval{start: start, end: end}
}
// size returns the size of this interval. Note that intervals are
// inclusive from both sides.
func (ri *reachabilityInterval) size() uint64 {
return ri.end - ri.start + 1
}
// splitInHalf splits this interval by a fraction of 0.5.
// See splitFraction for further details.
func (ri *reachabilityInterval) splitInHalf() (
left *reachabilityInterval, right *reachabilityInterval, err error) {
return ri.splitFraction(0.5)
}
// splitFraction splits this interval to two parts such that their
// union is equal to the original interval and the first (left) part
// contains the given fraction of the original interval's size.
// Note: if the split results in fractional parts, this method rounds
// the first part up and the last part down.
func (ri *reachabilityInterval) splitFraction(fraction float64) (
left *reachabilityInterval, right *reachabilityInterval, err error) {
if fraction < 0 || fraction > 1 {
return nil, nil, errors.Errorf("fraction must be between 0 and 1")
}
if ri.size() == 0 {
return nil, nil, errors.Errorf("cannot split an empty interval")
}
allocationSize := uint64(math.Ceil(float64(ri.size()) * fraction))
left = newReachabilityInterval(ri.start, ri.start+allocationSize-1)
right = newReachabilityInterval(ri.start+allocationSize, ri.end)
return left, right, nil
}
// splitExact splits this interval to exactly |sizes| parts where
// |part_i| = sizes[i]. This method expects sum(sizes) to be exactly
// equal to the interval's size.
func (ri *reachabilityInterval) splitExact(sizes []uint64) ([]*reachabilityInterval, error) {
sizesSum := uint64(0)
for _, size := range sizes {
sizesSum += size
}
if sizesSum != ri.size() {
return nil, errors.Errorf("sum of sizes must be equal to the interval's size")
}
intervals := make([]*reachabilityInterval, len(sizes))
start := ri.start
for i, size := range sizes {
intervals[i] = newReachabilityInterval(start, start+size-1)
start += size
}
return intervals, nil
}
// splitWithExponentialBias splits this interval to |sizes| parts
// by the allocation rule described below. This method expects sum(sizes)
// to be smaller or equal to the interval's size. Every part_i is
// allocated at least sizes[i] capacity. The remaining budget is
// split by an exponentially biased rule described below.
//
// This rule follows the GHOSTDAG protocol behavior where the child
// with the largest subtree is expected to dominate the competition
// for new blocks and thus grow the most. However, we may need to
// add slack for non-largest subtrees in order to make CPU reindexing
// attacks unworthy.
func (ri *reachabilityInterval) splitWithExponentialBias(sizes []uint64) ([]*reachabilityInterval, error) {
intervalSize := ri.size()
sizesSum := uint64(0)
for _, size := range sizes {
sizesSum += size
}
if sizesSum > intervalSize {
return nil, errors.Errorf("sum of sizes must be less than or equal to the interval's size")
}
if sizesSum == intervalSize {
return ri.splitExact(sizes)
}
// Add a fractional bias to every size in the given sizes
totalBias := intervalSize - sizesSum
remainingBias := totalBias
biasedSizes := make([]uint64, len(sizes))
fractions := exponentialFractions(sizes)
for i, fraction := range fractions {
var bias uint64
if i == len(fractions)-1 {
bias = remainingBias
} else {
bias = uint64(math.Round(float64(totalBias) * fraction))
if bias > remainingBias {
bias = remainingBias
}
}
biasedSizes[i] = sizes[i] + bias
remainingBias -= bias
}
return ri.splitExact(biasedSizes)
}
// exponentialFractions returns a fraction of each size in sizes
// as follows:
// fraction[i] = 2^size[i] / sum_j(2^size[j])
// In the code below the above equation is divided by 2^max(size)
// to avoid exploding numbers. Note that in 1 / 2^(max(size)-size[i])
// we divide 1 by potentially a very large number, which will
// result in loss of float precision. This is not a problem - all
// numbers close to 0 bear effectively the same weight.
func exponentialFractions(sizes []uint64) []float64 {
maxSize := uint64(0)
for _, size := range sizes {
if size > maxSize {
maxSize = size
}
}
fractions := make([]float64, len(sizes))
for i, size := range sizes {
fractions[i] = 1 / math.Pow(2, float64(maxSize-size))
}
fractionsSum := float64(0)
for _, fraction := range fractions {
fractionsSum += fraction
}
for i, fraction := range fractions {
fractions[i] = fraction / fractionsSum
}
return fractions
}
// isAncestorOf checks if this interval's node is a reachability tree
// ancestor of the other interval's node. The condition below is relying on the
// property of reachability intervals that intervals are either completely disjoint,
// or one strictly contains the other.
func (ri *reachabilityInterval) isAncestorOf(other *reachabilityInterval) bool {
return ri.start <= other.end && other.end <= ri.end
}
// String returns a string representation of the interval.
func (ri *reachabilityInterval) String() string {
return fmt.Sprintf("[%d,%d]", ri.start, ri.end)
}
// reachabilityTreeNode represents a node in the reachability tree
// of some DAG block. It mainly provides the ability to query *tree*
// reachability with O(1) query time. It does so by managing an
// index interval for each node and making sure all nodes in its
// subtree are indexed within the interval, so the query
// B ∈ subtree(A) simply becomes B.interval ⊂ A.interval.
//
// The main challenge of maintaining such intervals is that our tree
// is an ever-growing tree and as such pre-allocated intervals may
// not suffice as per future events. This is where the reindexing
// algorithm below comes into place.
// We use the reasonable assumption that the initial root interval
// (e.g., [0, 2^64-1]) should always suffice for any practical use-
// case, and so reindexing should always succeed unless more than
// 2^64 blocks are added to the DAG/tree.
type reachabilityTreeNode struct {
blockNode *blockNode
children []*reachabilityTreeNode
parent *reachabilityTreeNode
// interval is the index interval containing all intervals of
// blocks in this node's subtree
interval *reachabilityInterval
// remainingInterval is the not-yet allocated interval (within
// this node's interval) awaiting new children
remainingInterval *reachabilityInterval
}
func newReachabilityTreeNode(blockNode *blockNode) *reachabilityTreeNode {
// Please see the comment above reachabilityTreeNode to understand why
// we use these initial values.
interval := newReachabilityInterval(1, math.MaxUint64-1)
// We subtract 1 from the end of the remaining interval to prevent the node from allocating
// the entire interval to its child, so its interval would *strictly* contain the interval of its child.
remainingInterval := newReachabilityInterval(interval.start, interval.end-1)
return &reachabilityTreeNode{blockNode: blockNode, interval: interval, remainingInterval: remainingInterval}
}
// addChild adds child to this tree node. If this node has no
// remaining interval to allocate, a reindexing is triggered.
// This method returns a list of reachabilityTreeNodes modified
// by it.
func (rtn *reachabilityTreeNode) addChild(child *reachabilityTreeNode) ([]*reachabilityTreeNode, error) {
// Set the parent-child relationship
rtn.children = append(rtn.children, child)
child.parent = rtn
// No allocation space left -- reindex
if rtn.remainingInterval.size() == 0 {
reindexStartTime := time.Now()
modifiedNodes, err := rtn.reindexIntervals()
if err != nil {
return nil, err
}
reindexTimeElapsed := time.Since(reindexStartTime)
log.Debugf("Reachability reindex triggered for "+
"block %s. Modified %d tree nodes and took %dms.",
rtn.blockNode.hash, len(modifiedNodes), reindexTimeElapsed.Milliseconds())
return modifiedNodes, nil
}
// Allocate from the remaining space
allocated, remaining, err := rtn.remainingInterval.splitInHalf()
if err != nil {
return nil, err
}
child.setInterval(allocated)
rtn.remainingInterval = remaining
return []*reachabilityTreeNode{rtn, child}, nil
}
// setInterval sets the reachability interval for this node.
func (rtn *reachabilityTreeNode) setInterval(interval *reachabilityInterval) {
rtn.interval = interval
// Reserve a single interval index for the current node. This
// is necessary to ensure that ancestor intervals are strictly
// supersets of any descendant intervals and not equal
rtn.remainingInterval = newReachabilityInterval(interval.start, interval.end-1)
}
// reindexIntervals traverses the reachability subtree that's
// defined by this node and reallocates reachability interval space
// such that another reindexing is unlikely to occur shortly
// thereafter. It does this by traversing down the reachability
// tree until it finds a node with a subreeSize that's greater than
// its interval size. See propagateInterval for further details.
// This method returns a list of reachabilityTreeNodes modified by it.
func (rtn *reachabilityTreeNode) reindexIntervals() ([]*reachabilityTreeNode, error) {
current := rtn
// Initial interval and subtree sizes
intervalSize := current.interval.size()
subTreeSizeMap := make(map[*reachabilityTreeNode]uint64)
current.countSubtrees(subTreeSizeMap)
currentSubtreeSize := subTreeSizeMap[current]
// Find the first ancestor that has sufficient interval space
for intervalSize < currentSubtreeSize {
if current.parent == nil {
// If we ended up here it means that there are more
// than 2^64 blocks, which shouldn't ever happen.
return nil, errors.Errorf("missing tree " +
"parent during reindexing. Theoretically, this " +
"should only ever happen if there are more " +
"than 2^64 blocks in the DAG.")
}
current = current.parent
intervalSize = current.interval.size()
current.countSubtrees(subTreeSizeMap)
currentSubtreeSize = subTreeSizeMap[current]
}
// Propagate the interval to the subtree
return current.propagateInterval(subTreeSizeMap)
}
// countSubtrees counts the size of each subtree under this node,
// and populates the provided subTreeSizeMap with the results.
// It is equivalent to the following recursive implementation:
//
// func (rtn *reachabilityTreeNode) countSubtrees() uint64 {
// subtreeSize := uint64(0)
// for _, child := range rtn.children {
// subtreeSize += child.countSubtrees()
// }
// return subtreeSize + 1
// }
//
// However, we are expecting (linearly) deep trees, and so a
// recursive stack-based approach is inefficient and will hit
// recursion limits. Instead, the same logic was implemented
// using a (queue-based) BFS method. At a high level, the
// algorithm uses BFS for reaching all leaves and pushes
// intermediate updates from leaves via parent chains until all
// size information is gathered at the root of the operation
// (i.e. at rtn).
func (rtn *reachabilityTreeNode) countSubtrees(subTreeSizeMap map[*reachabilityTreeNode]uint64) {
queue := []*reachabilityTreeNode{rtn}
calculatedChildrenCount := make(map[*reachabilityTreeNode]uint64)
for len(queue) > 0 {
var current *reachabilityTreeNode
current, queue = queue[0], queue[1:]
if len(current.children) == 0 {
// We reached a leaf
subTreeSizeMap[current] = 1
} else if _, ok := subTreeSizeMap[current]; !ok {
// We haven't yet calculated the subtree size of
// the current node. Add all its children to the
// queue
queue = append(queue, current.children...)
continue
}
// We reached a leaf or a pre-calculated subtree.
// Push information up
for current != rtn {
current = current.parent
calculatedChildrenCount[current]++
if calculatedChildrenCount[current] != uint64(len(current.children)) {
// Not all subtrees of the current node are ready
break
}
// All children of `current` have calculated their subtree size.
// Sum them all together and add 1 to get the sub tree size of
// `current`.
childSubtreeSizeSum := uint64(0)
for _, child := range current.children {
childSubtreeSizeSum += subTreeSizeMap[child]
}
subTreeSizeMap[current] = childSubtreeSizeSum + 1
}
}
}
// propagateInterval propagates the new interval using a BFS traversal.
// Subtree intervals are recursively allocated according to subtree sizes and
// the allocation rule in splitWithExponentialBias. This method returns
// a list of reachabilityTreeNodes modified by it.
func (rtn *reachabilityTreeNode) propagateInterval(subTreeSizeMap map[*reachabilityTreeNode]uint64) ([]*reachabilityTreeNode, error) {
// We set the interval to reset its remainingInterval, so we could reallocate it while reindexing.
rtn.setInterval(rtn.interval)
queue := []*reachabilityTreeNode{rtn}
var modifiedNodes []*reachabilityTreeNode
for len(queue) > 0 {
var current *reachabilityTreeNode
current, queue = queue[0], queue[1:]
if len(current.children) > 0 {
sizes := make([]uint64, len(current.children))
for i, child := range current.children {
sizes[i] = subTreeSizeMap[child]
}
intervals, err := current.remainingInterval.splitWithExponentialBias(sizes)
if err != nil {
return nil, err
}
for i, child := range current.children {
childInterval := intervals[i]
child.setInterval(childInterval)
queue = append(queue, child)
}
// Empty up remaining interval
current.remainingInterval.start = current.remainingInterval.end + 1
}
modifiedNodes = append(modifiedNodes, current)
}
return modifiedNodes, nil
}
// isAncestorOf checks if this node is a reachability tree ancestor
// of the other node.
func (rtn *reachabilityTreeNode) isAncestorOf(other *reachabilityTreeNode) bool {
return rtn.interval.isAncestorOf(other.interval)
}
// String returns a string representation of a reachability tree node
// and its children.
func (rtn *reachabilityTreeNode) String() string {
queue := []*reachabilityTreeNode{rtn}
lines := []string{rtn.interval.String()}
for len(queue) > 0 {
var current *reachabilityTreeNode
current, queue = queue[0], queue[1:]
if len(current.children) == 0 {
continue
}
line := ""
for _, child := range current.children {
line += child.interval.String()
queue = append(queue, child)
}
lines = append([]string{line}, lines...)
}
return strings.Join(lines, "\n")
}
// futureCoveringBlockSet represents a collection of blocks in the future of
// a certain block. Once a block B is added to the DAG, every block A_i in
// B's selected parent anticone must register B in its futureCoveringBlockSet. This allows
// to relatively quickly (O(log(|futureCoveringBlockSet|))) query whether B
// is a descendent (is in the "future") of any block that previously
// registered it.
//
// Note that futureCoveringBlockSet is meant to be queried only if B is not
// a reachability tree descendant of the block in question, as reachability
// tree queries are always O(1).
//
// See insertBlock, isInFuture, and dag.isAncestorOf for further details.
type futureCoveringBlockSet []*futureCoveringBlock
// futureCoveringBlock represents a block in the future of some other block.
type futureCoveringBlock struct {
blockNode *blockNode
treeNode *reachabilityTreeNode
}
// insertBlock inserts the given block into this futureCoveringBlockSet
// while keeping futureCoveringBlockSet ordered by interval.
// If a block B ∈ futureCoveringBlockSet exists such that its interval
// contains block's interval, block need not be added. If block's
// interval contains B's interval, it replaces it.
//
// Notes:
// * Intervals never intersect unless one contains the other
// (this follows from the tree structure and the indexing rule).
// * Since futureCoveringBlockSet is kept ordered, a binary search can be
// used for insertion/queries.
// * Although reindexing may change a block's interval, the
// is-superset relation will by definition
// be always preserved.
func (fb *futureCoveringBlockSet) insertBlock(block *futureCoveringBlock) {
blockInterval := block.treeNode.interval
i := fb.findIndex(block)
if i > 0 {
candidate := (*fb)[i-1]
candidateInterval := candidate.treeNode.interval
if candidateInterval.isAncestorOf(blockInterval) {
// candidate is an ancestor of block, no need to insert
return
}
if blockInterval.isAncestorOf(candidateInterval) {
// block is an ancestor of candidate, and can thus replace it
(*fb)[i-1] = block
return
}
}
// Insert block in the correct index to maintain futureCoveringBlockSet as
// a sorted-by-interval list.
// Note that i might be equal to len(futureCoveringBlockSet)
left := (*fb)[:i]
right := append([]*futureCoveringBlock{block}, (*fb)[i:]...)
*fb = append(left, right...)
}
// isInFuture resolves whether the given block is in the subtree of
// any block in this futureCoveringBlockSet.
// See insertBlock method for the complementary insertion behavior.
//
// Like the insert method, this method also relies on the fact that
// futureCoveringBlockSet is kept ordered by interval to efficiently perform a
// binary search over futureCoveringBlockSet and answer the query in
// O(log(|futureCoveringBlockSet|)).
func (fb futureCoveringBlockSet) isInFuture(block *futureCoveringBlock) bool {
i := fb.findIndex(block)
if i == 0 {
// No candidate to contain block
return false
}
candidate := fb[i-1]
return candidate.treeNode.isAncestorOf(block.treeNode)
}
// findIndex finds the index of the block with the maximum start that is below
// the given block.
func (fb futureCoveringBlockSet) findIndex(block *futureCoveringBlock) int {
blockInterval := block.treeNode.interval
end := blockInterval.end
low := 0
high := len(fb)
for low < high {
middle := (low + high) / 2
middleInterval := fb[middle].treeNode.interval
if end < middleInterval.start {
high = middle
} else {
low = middle + 1
}
}
return low
}
// String returns a string representation of the intervals in this futureCoveringBlockSet.
func (fb futureCoveringBlockSet) String() string {
intervalsString := ""
for _, block := range fb {
intervalsString += block.treeNode.interval.String()
}
return intervalsString
}
func (dag *BlockDAG) updateReachability(node *blockNode, selectedParentAnticone []*blockNode) error {
// Allocate a new reachability tree node
newTreeNode := newReachabilityTreeNode(node)
// If this is the genesis node, simply initialize it and return
if node.isGenesis() {
dag.reachabilityStore.setTreeNode(newTreeNode)
return nil
}
// Insert the node into the selected parent's reachability tree
selectedParentTreeNode, err := dag.reachabilityStore.treeNodeByBlockNode(node.selectedParent)
if err != nil {
return err
}
modifiedTreeNodes, err := selectedParentTreeNode.addChild(newTreeNode)
if err != nil {
return err
}
for _, modifiedTreeNode := range modifiedTreeNodes {
dag.reachabilityStore.setTreeNode(modifiedTreeNode)
}
// Add the block to the futureCoveringSets of all the blocks
// in the selected parent's anticone
for _, current := range selectedParentAnticone {
currentFutureCoveringSet, err := dag.reachabilityStore.futureCoveringSetByBlockNode(current)
if err != nil {
return err
}
currentFutureCoveringSet.insertBlock(&futureCoveringBlock{blockNode: node, treeNode: newTreeNode})
err = dag.reachabilityStore.setFutureCoveringSet(current, currentFutureCoveringSet)
if err != nil {
return err
}
}
return nil
}
// isAncestorOf returns true if this node is in the past of the other node
// in the DAG. The complexity of this method is O(log(|this.futureCoveringBlockSet|))
func (dag *BlockDAG) isAncestorOf(this *blockNode, other *blockNode) (bool, error) {
// First, check if this node is a reachability tree ancestor of the
// other node
thisTreeNode, err := dag.reachabilityStore.treeNodeByBlockNode(this)
if err != nil {
return false, err
}
otherTreeNode, err := dag.reachabilityStore.treeNodeByBlockNode(other)
if err != nil {
return false, err
}
if thisTreeNode.isAncestorOf(otherTreeNode) {
return true, nil
}
// Otherwise, use previously registered future blocks to complete the
// reachability test
thisFutureCoveringSet, err := dag.reachabilityStore.futureCoveringSetByBlockNode(this)
if err != nil {
return false, err
}
return thisFutureCoveringSet.isInFuture(&futureCoveringBlock{blockNode: other, treeNode: otherTreeNode}), nil
}

View File

@@ -0,0 +1,688 @@
package blockdag
import (
"reflect"
"strings"
"testing"
)
func TestAddChild(t *testing.T) {
// Scenario 1: test addChild in a chain
// root -> a -> b -> c...
// Create the root node of a new reachability tree
root := newReachabilityTreeNode(&blockNode{})
root.setInterval(newReachabilityInterval(1, 100))
// Add a chain of child nodes just before a reindex occurs (2^6=64 < 100)
currentTip := root
for i := 0; i < 6; i++ {
node := newReachabilityTreeNode(&blockNode{})
modifiedNodes, err := currentTip.addChild(node)
if err != nil {
t.Fatalf("TestAddChild: addChild failed: %s", err)
}
// Expect only the node and its parent to be affected
expectedModifiedNodes := []*reachabilityTreeNode{currentTip, node}
if !reflect.DeepEqual(modifiedNodes, expectedModifiedNodes) {
t.Fatalf("TestAddChild: unexpected modifiedNodes. "+
"want: %s, got: %s", expectedModifiedNodes, modifiedNodes)
}
currentTip = node
}
// Add another node to the tip of the chain to trigger a reindex (100 < 2^7=128)
lastChild := newReachabilityTreeNode(&blockNode{})
modifiedNodes, err := currentTip.addChild(lastChild)
if err != nil {
t.Fatalf("TestAddChild: addChild failed: %s", err)
}
// Expect more than just the node and its parent to be modified but not
// all the nodes
if len(modifiedNodes) <= 2 && len(modifiedNodes) >= 7 {
t.Fatalf("TestAddChild: unexpected amount of modifiedNodes.")
}
// Expect the tip to have an interval of 1 and remaining interval of 0
tipInterval := lastChild.interval.size()
if tipInterval != 1 {
t.Fatalf("TestAddChild: unexpected tip interval size: want: 1, got: %d", tipInterval)
}
tipRemainingInterval := lastChild.remainingInterval.size()
if tipRemainingInterval != 0 {
t.Fatalf("TestAddChild: unexpected tip interval size: want: 0, got: %d", tipRemainingInterval)
}
// Expect all nodes to be descendant nodes of root
currentNode := currentTip
for currentNode != nil {
if !root.isAncestorOf(currentNode) {
t.Fatalf("TestAddChild: currentNode is not a descendant of root")
}
currentNode = currentNode.parent
}
// Scenario 2: test addChild where all nodes are direct descendants of root
// root -> a, b, c...
// Create the root node of a new reachability tree
root = newReachabilityTreeNode(&blockNode{})
root.setInterval(newReachabilityInterval(1, 100))
// Add child nodes to root just before a reindex occurs (2^6=64 < 100)
childNodes := make([]*reachabilityTreeNode, 6)
for i := 0; i < len(childNodes); i++ {
childNodes[i] = newReachabilityTreeNode(&blockNode{})
modifiedNodes, err := root.addChild(childNodes[i])
if err != nil {
t.Fatalf("TestAddChild: addChild failed: %s", err)
}
// Expect only the node and the root to be affected
expectedModifiedNodes := []*reachabilityTreeNode{root, childNodes[i]}
if !reflect.DeepEqual(modifiedNodes, expectedModifiedNodes) {
t.Fatalf("TestAddChild: unexpected modifiedNodes. "+
"want: %s, got: %s", expectedModifiedNodes, modifiedNodes)
}
}
// Add another node to the root to trigger a reindex (100 < 2^7=128)
lastChild = newReachabilityTreeNode(&blockNode{})
modifiedNodes, err = root.addChild(lastChild)
if err != nil {
t.Fatalf("TestAddChild: addChild failed: %s", err)
}
// Expect more than just the node and the root to be modified but not
// all the nodes
if len(modifiedNodes) <= 2 && len(modifiedNodes) >= 7 {
t.Fatalf("TestAddChild: unexpected amount of modifiedNodes.")
}
// Expect the last-added child to have an interval of 1 and remaining interval of 0
lastChildInterval := lastChild.interval.size()
if lastChildInterval != 1 {
t.Fatalf("TestAddChild: unexpected lastChild interval size: want: 1, got: %d", lastChildInterval)
}
lastChildRemainingInterval := lastChild.remainingInterval.size()
if lastChildRemainingInterval != 0 {
t.Fatalf("TestAddChild: unexpected lastChild interval size: want: 0, got: %d", lastChildRemainingInterval)
}
// Expect all nodes to be descendant nodes of root
for _, childNode := range childNodes {
if !root.isAncestorOf(childNode) {
t.Fatalf("TestAddChild: childNode is not a descendant of root")
}
}
}
func TestSplitFraction(t *testing.T) {
tests := []struct {
interval *reachabilityInterval
fraction float64
expectedLeft *reachabilityInterval
expectedRight *reachabilityInterval
}{
{
interval: newReachabilityInterval(1, 100),
fraction: 0.5,
expectedLeft: newReachabilityInterval(1, 50),
expectedRight: newReachabilityInterval(51, 100),
},
{
interval: newReachabilityInterval(2, 100),
fraction: 0.5,
expectedLeft: newReachabilityInterval(2, 51),
expectedRight: newReachabilityInterval(52, 100),
},
{
interval: newReachabilityInterval(1, 99),
fraction: 0.5,
expectedLeft: newReachabilityInterval(1, 50),
expectedRight: newReachabilityInterval(51, 99),
},
{
interval: newReachabilityInterval(1, 100),
fraction: 0.2,
expectedLeft: newReachabilityInterval(1, 20),
expectedRight: newReachabilityInterval(21, 100),
},
{
interval: newReachabilityInterval(1, 100),
fraction: 0,
expectedLeft: newReachabilityInterval(1, 0),
expectedRight: newReachabilityInterval(1, 100),
},
{
interval: newReachabilityInterval(1, 100),
fraction: 1,
expectedLeft: newReachabilityInterval(1, 100),
expectedRight: newReachabilityInterval(101, 100),
},
}
for i, test := range tests {
left, right, err := test.interval.splitFraction(test.fraction)
if err != nil {
t.Fatalf("TestSplitFraction: splitFraction unexpectedly failed in test #%d: %s", i, err)
}
if !reflect.DeepEqual(left, test.expectedLeft) {
t.Errorf("TestSplitFraction: unexpected left in test #%d. "+
"want: %s, got: %s", i, test.expectedLeft, left)
}
if !reflect.DeepEqual(right, test.expectedRight) {
t.Errorf("TestSplitFraction: unexpected right in test #%d. "+
"want: %s, got: %s", i, test.expectedRight, right)
}
}
}
func TestSplitExact(t *testing.T) {
tests := []struct {
interval *reachabilityInterval
sizes []uint64
expectedIntervals []*reachabilityInterval
}{
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{100},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{50, 50},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 50),
newReachabilityInterval(51, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{10, 20, 30, 40},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 10),
newReachabilityInterval(11, 30),
newReachabilityInterval(31, 60),
newReachabilityInterval(61, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{0, 100},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 0),
newReachabilityInterval(1, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{100, 0},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 100),
newReachabilityInterval(101, 100),
},
},
}
for i, test := range tests {
intervals, err := test.interval.splitExact(test.sizes)
if err != nil {
t.Fatalf("TestSplitExact: splitExact unexpectedly failed in test #%d: %s", i, err)
}
if !reflect.DeepEqual(intervals, test.expectedIntervals) {
t.Errorf("TestSplitExact: unexpected intervals in test #%d. "+
"want: %s, got: %s", i, test.expectedIntervals, intervals)
}
}
}
func TestSplitWithExponentialBias(t *testing.T) {
tests := []struct {
interval *reachabilityInterval
sizes []uint64
expectedIntervals []*reachabilityInterval
}{
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{100},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{50, 50},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 50),
newReachabilityInterval(51, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{10, 20, 30, 40},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 10),
newReachabilityInterval(11, 30),
newReachabilityInterval(31, 60),
newReachabilityInterval(61, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{25, 25},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 50),
newReachabilityInterval(51, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{1, 1},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 50),
newReachabilityInterval(51, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{33, 33, 33},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 33),
newReachabilityInterval(34, 66),
newReachabilityInterval(67, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{10, 15, 25},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 10),
newReachabilityInterval(11, 25),
newReachabilityInterval(26, 100),
},
},
{
interval: newReachabilityInterval(1, 100),
sizes: []uint64{25, 15, 10},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 75),
newReachabilityInterval(76, 90),
newReachabilityInterval(91, 100),
},
},
{
interval: newReachabilityInterval(1, 10_000),
sizes: []uint64{10, 10, 20},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 20),
newReachabilityInterval(21, 40),
newReachabilityInterval(41, 10_000),
},
},
{
interval: newReachabilityInterval(1, 100_000),
sizes: []uint64{31_000, 31_000, 30_001},
expectedIntervals: []*reachabilityInterval{
newReachabilityInterval(1, 35_000),
newReachabilityInterval(35_001, 69_999),
newReachabilityInterval(70_000, 100_000),
},
},
}
for i, test := range tests {
intervals, err := test.interval.splitWithExponentialBias(test.sizes)
if err != nil {
t.Fatalf("TestSplitWithExponentialBias: splitWithExponentialBias unexpectedly failed in test #%d: %s", i, err)
}
if !reflect.DeepEqual(intervals, test.expectedIntervals) {
t.Errorf("TestSplitWithExponentialBias: unexpected intervals in test #%d. "+
"want: %s, got: %s", i, test.expectedIntervals, intervals)
}
}
}
func TestIsInFuture(t *testing.T) {
blocks := futureCoveringBlockSet{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(2, 3)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
}
tests := []struct {
block *futureCoveringBlock
expectedResult bool
}{
{
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 1)}},
expectedResult: false,
},
{
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(5, 7)}},
expectedResult: true,
},
{
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 76)}},
expectedResult: true,
},
{
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(78, 100)}},
expectedResult: false,
},
{
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1980, 2000)}},
expectedResult: false,
},
{
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1920)}},
expectedResult: true,
},
}
for i, test := range tests {
result := blocks.isInFuture(test.block)
if result != test.expectedResult {
t.Errorf("TestIsInFuture: unexpected result in test #%d. Want: %t, got: %t",
i, test.expectedResult, result)
}
}
}
func TestInsertBlock(t *testing.T) {
blocks := futureCoveringBlockSet{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
}
tests := []struct {
toInsert []*futureCoveringBlock
expectedResult futureCoveringBlockSet
}{
{
toInsert: []*futureCoveringBlock{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(5, 7)}},
},
expectedResult: futureCoveringBlockSet{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
},
},
{
toInsert: []*futureCoveringBlock{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(65, 78)}},
},
expectedResult: futureCoveringBlockSet{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(65, 78)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
},
},
{
toInsert: []*futureCoveringBlock{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
},
expectedResult: futureCoveringBlockSet{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
},
},
{
toInsert: []*futureCoveringBlock{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(3000, 3010)}},
},
expectedResult: futureCoveringBlockSet{
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(3000, 3010)}},
},
},
}
for i, test := range tests {
// Create a clone of blocks so that we have a clean start for every test
blocksClone := make(futureCoveringBlockSet, len(blocks))
for i, block := range blocks {
blocksClone[i] = block
}
for _, block := range test.toInsert {
blocksClone.insertBlock(block)
}
if !reflect.DeepEqual(blocksClone, test.expectedResult) {
t.Errorf("TestInsertBlock: unexpected result in test #%d. Want: %s, got: %s",
i, test.expectedResult, blocksClone)
}
}
}
func TestSplitFractionErrors(t *testing.T) {
interval := newReachabilityInterval(100, 200)
// Negative fraction
_, _, err := interval.splitFraction(-0.5)
if err == nil {
t.Fatalf("TestSplitFractionErrors: splitFraction unexpectedly " +
"didn't return an error for a negative fraction")
}
expectedErrSubstring := "fraction must be between 0 and 1"
if !strings.Contains(err.Error(), expectedErrSubstring) {
t.Fatalf("TestSplitFractionErrors: splitFraction returned wrong error "+
"for a negative fraction. "+
"Want: %s, got: %s", expectedErrSubstring, err)
}
// Fraction > 1
_, _, err = interval.splitFraction(1.5)
if err == nil {
t.Fatalf("TestSplitFractionErrors: splitFraction unexpectedly " +
"didn't return an error for a fraction greater than 1")
}
expectedErrSubstring = "fraction must be between 0 and 1"
if !strings.Contains(err.Error(), expectedErrSubstring) {
t.Fatalf("TestSplitFractionErrors: splitFraction returned wrong error "+
"for a fraction greater than 1. "+
"Want: %s, got: %s", expectedErrSubstring, err)
}
// Splitting an empty interval
emptyInterval := newReachabilityInterval(1, 0)
_, _, err = emptyInterval.splitFraction(0.5)
if err == nil {
t.Fatalf("TestSplitFractionErrors: splitFraction unexpectedly " +
"didn't return an error for an empty interval")
}
expectedErrSubstring = "cannot split an empty interval"
if !strings.Contains(err.Error(), expectedErrSubstring) {
t.Fatalf("TestSplitFractionErrors: splitFraction returned wrong error "+
"for an empty interval. "+
"Want: %s, got: %s", expectedErrSubstring, err)
}
}
func TestSplitExactErrors(t *testing.T) {
interval := newReachabilityInterval(100, 199)
// Sum of sizes greater than the size of the interval
sizes := []uint64{50, 51}
_, err := interval.splitExact(sizes)
if err == nil {
t.Fatalf("TestSplitExactErrors: splitExact unexpectedly " +
"didn't return an error for (sum of sizes) > (size of interval)")
}
expectedErrSubstring := "sum of sizes must be equal to the interval's size"
if !strings.Contains(err.Error(), expectedErrSubstring) {
t.Fatalf("TestSplitExactErrors: splitExact returned wrong error "+
"for (sum of sizes) > (size of interval). "+
"Want: %s, got: %s", expectedErrSubstring, err)
}
// Sum of sizes smaller than the size of the interval
sizes = []uint64{50, 49}
_, err = interval.splitExact(sizes)
if err == nil {
t.Fatalf("TestSplitExactErrors: splitExact unexpectedly " +
"didn't return an error for (sum of sizes) < (size of interval)")
}
expectedErrSubstring = "sum of sizes must be equal to the interval's size"
if !strings.Contains(err.Error(), expectedErrSubstring) {
t.Fatalf("TestSplitExactErrors: splitExact returned wrong error "+
"for (sum of sizes) < (size of interval). "+
"Want: %s, got: %s", expectedErrSubstring, err)
}
}
func TestSplitWithExponentialBiasErrors(t *testing.T) {
interval := newReachabilityInterval(100, 199)
// Sum of sizes greater than the size of the interval
sizes := []uint64{50, 51}
_, err := interval.splitWithExponentialBias(sizes)
if err == nil {
t.Fatalf("TestSplitWithExponentialBiasErrors: splitWithExponentialBias " +
"unexpectedly didn't return an error")
}
expectedErrSubstring := "sum of sizes must be less than or equal to the interval's size"
if !strings.Contains(err.Error(), expectedErrSubstring) {
t.Fatalf("TestSplitWithExponentialBiasErrors: splitWithExponentialBias "+
"returned wrong error. Want: %s, got: %s", expectedErrSubstring, err)
}
}
func TestReindexIntervalErrors(t *testing.T) {
// Create a treeNode and give it size = 100
treeNode := newReachabilityTreeNode(&blockNode{})
treeNode.setInterval(newReachabilityInterval(0, 99))
// Add a chain of 100 child treeNodes to treeNode
var err error
currentTreeNode := treeNode
for i := 0; i < 100; i++ {
childTreeNode := newReachabilityTreeNode(&blockNode{})
_, err = currentTreeNode.addChild(childTreeNode)
if err != nil {
break
}
currentTreeNode = childTreeNode
}
// At the 100th addChild we expect a reindex. This reindex should
// fail because our initial treeNode only has size = 100, and the
// reindex requires size > 100.
// This simulates the case when (somehow) there's more than 2^64
// blocks in the DAG, since the genesis block has size = 2^64.
if err == nil {
t.Fatalf("TestReindexIntervalErrors: reindexIntervals " +
"unexpectedly didn't return an error")
}
if !strings.Contains(err.Error(), "missing tree parent during reindexing") {
t.Fatalf("TestReindexIntervalErrors: reindexIntervals "+
"returned an expected error: %s", err)
}
}
func BenchmarkReindexInterval(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
root := newReachabilityTreeNode(&blockNode{})
const subTreeSize = 70000
// We set the interval of the root to subTreeSize*2 because
// its first child gets half of the interval, so a reindex
// from the root should happen after adding subTreeSize
// nodes.
root.setInterval(newReachabilityInterval(0, subTreeSize*2))
currentTreeNode := root
for i := 0; i < subTreeSize; i++ {
childTreeNode := newReachabilityTreeNode(&blockNode{})
_, err := currentTreeNode.addChild(childTreeNode)
if err != nil {
b.Fatalf("addChild: %s", err)
}
currentTreeNode = childTreeNode
}
remainingIntervalBefore := *root.remainingInterval
// After we added subTreeSize nodes, adding the next
// node should lead to a reindex from root.
fullReindexTriggeringNode := newReachabilityTreeNode(&blockNode{})
b.StartTimer()
_, err := currentTreeNode.addChild(fullReindexTriggeringNode)
b.StopTimer()
if err != nil {
b.Fatalf("addChild: %s", err)
}
if *root.remainingInterval == remainingIntervalBefore {
b.Fatal("Expected a reindex from root, but it didn't happen")
}
}
}
func TestFutureCoveringBlockSetString(t *testing.T) {
treeNodeA := newReachabilityTreeNode(&blockNode{})
treeNodeA.setInterval(newReachabilityInterval(123, 456))
treeNodeB := newReachabilityTreeNode(&blockNode{})
treeNodeB.setInterval(newReachabilityInterval(457, 789))
futureCoveringSet := futureCoveringBlockSet{
&futureCoveringBlock{treeNode: treeNodeA},
&futureCoveringBlock{treeNode: treeNodeB},
}
str := futureCoveringSet.String()
expectedStr := "[123,456][457,789]"
if str != expectedStr {
t.Fatalf("TestFutureCoveringBlockSetString: unexpected "+
"string. Want: %s, got: %s", expectedStr, str)
}
}
func TestReachabilityTreeNodeString(t *testing.T) {
treeNodeA := newReachabilityTreeNode(&blockNode{})
treeNodeA.setInterval(newReachabilityInterval(100, 199))
treeNodeB1 := newReachabilityTreeNode(&blockNode{})
treeNodeB1.setInterval(newReachabilityInterval(100, 150))
treeNodeB2 := newReachabilityTreeNode(&blockNode{})
treeNodeB2.setInterval(newReachabilityInterval(150, 199))
treeNodeC := newReachabilityTreeNode(&blockNode{})
treeNodeC.setInterval(newReachabilityInterval(100, 149))
treeNodeA.children = []*reachabilityTreeNode{treeNodeB1, treeNodeB2}
treeNodeB2.children = []*reachabilityTreeNode{treeNodeC}
str := treeNodeA.String()
expectedStr := "[100,149]\n[100,150][150,199]\n[100,199]"
if str != expectedStr {
t.Fatalf("TestReachabilityTreeNodeString: unexpected "+
"string. Want: %s, got: %s", expectedStr, str)
}
}

View File

@@ -2,17 +2,17 @@ package blockdag
import (
"bytes"
"github.com/kaspanet/kaspad/infrastructure/database"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/database"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
"github.com/pkg/errors"
"io"
)
type reachabilityData struct {
treeNode *reachabilityTreeNode
futureCoveringSet futureCoveringTreeNodeSet
futureCoveringSet futureCoveringBlockSet
}
type reachabilityStore struct {
@@ -41,11 +41,11 @@ func (store *reachabilityStore) setTreeNode(treeNode *reachabilityTreeNode) {
store.setBlockAsDirty(node.hash)
}
func (store *reachabilityStore) setFutureCoveringSet(node *blockNode, futureCoveringSet futureCoveringTreeNodeSet) error {
func (store *reachabilityStore) setFutureCoveringSet(node *blockNode, futureCoveringSet futureCoveringBlockSet) error {
// load the reachability data from DB to store.loaded
_, exists := store.reachabilityDataByHash(node.hash)
if !exists {
return reachabilityNotFoundError(node.hash)
return reachabilityNotFoundError(node)
}
store.loaded[*node.hash].futureCoveringSet = futureCoveringSet
@@ -57,26 +57,22 @@ func (store *reachabilityStore) setBlockAsDirty(blockHash *daghash.Hash) {
store.dirty[*blockHash] = struct{}{}
}
func reachabilityNotFoundError(hash *daghash.Hash) error {
return errors.Errorf("couldn't find reachability data for block %s", hash)
func reachabilityNotFoundError(node *blockNode) error {
return errors.Errorf("Couldn't find reachability data for block %s", node.hash)
}
func (store *reachabilityStore) treeNodeByBlockHash(hash *daghash.Hash) (*reachabilityTreeNode, error) {
reachabilityData, exists := store.reachabilityDataByHash(hash)
func (store *reachabilityStore) treeNodeByBlockNode(node *blockNode) (*reachabilityTreeNode, error) {
reachabilityData, exists := store.reachabilityDataByHash(node.hash)
if !exists {
return nil, reachabilityNotFoundError(hash)
return nil, reachabilityNotFoundError(node)
}
return reachabilityData.treeNode, nil
}
func (store *reachabilityStore) treeNodeByBlockNode(node *blockNode) (*reachabilityTreeNode, error) {
return store.treeNodeByBlockHash(node.hash)
}
func (store *reachabilityStore) futureCoveringSetByBlockNode(node *blockNode) (futureCoveringTreeNodeSet, error) {
func (store *reachabilityStore) futureCoveringSetByBlockNode(node *blockNode) (futureCoveringBlockSet, error) {
reachabilityData, exists := store.reachabilityDataByHash(node.hash)
if !exists {
return nil, reachabilityNotFoundError(node.hash)
return nil, reachabilityNotFoundError(node)
}
return reachabilityData.futureCoveringSet, nil
}
@@ -180,10 +176,7 @@ func (store *reachabilityStore) loadReachabilityDataFromCursor(cursor database.C
}
// Connect the treeNode with its blockNode
reachabilityData.treeNode.blockNode, ok = store.dag.index.LookupNode(hash)
if !ok {
return errors.Errorf("block %s does not exist in the DAG", hash)
}
reachabilityData.treeNode.blockNode = store.dag.index.LookupNode(hash)
return nil
}
@@ -219,26 +212,32 @@ func (store *reachabilityStore) serializeTreeNode(w io.Writer, treeNode *reachab
return err
}
// Serialize the remaining interval
err = store.serializeReachabilityInterval(w, treeNode.remainingInterval)
if err != nil {
return err
}
// Serialize the parent
// If this is the genesis block, write the zero hash instead
parentHash := &daghash.ZeroHash
if treeNode.parent != nil {
parentHash = treeNode.parent.blockNode.hash
}
err = domainmessage.WriteElement(w, parentHash)
err = wire.WriteElement(w, parentHash)
if err != nil {
return err
}
// Serialize the amount of children
err = domainmessage.WriteVarInt(w, uint64(len(treeNode.children)))
err = wire.WriteVarInt(w, uint64(len(treeNode.children)))
if err != nil {
return err
}
// Serialize the children
for _, child := range treeNode.children {
err = domainmessage.WriteElement(w, child.blockNode.hash)
err = wire.WriteElement(w, child.blockNode.hash)
if err != nil {
return err
}
@@ -249,13 +248,13 @@ func (store *reachabilityStore) serializeTreeNode(w io.Writer, treeNode *reachab
func (store *reachabilityStore) serializeReachabilityInterval(w io.Writer, interval *reachabilityInterval) error {
// Serialize start
err := domainmessage.WriteElement(w, interval.start)
err := wire.WriteElement(w, interval.start)
if err != nil {
return err
}
// Serialize end
err = domainmessage.WriteElement(w, interval.end)
err = wire.WriteElement(w, interval.end)
if err != nil {
return err
}
@@ -263,16 +262,16 @@ func (store *reachabilityStore) serializeReachabilityInterval(w io.Writer, inter
return nil
}
func (store *reachabilityStore) serializeFutureCoveringSet(w io.Writer, futureCoveringSet futureCoveringTreeNodeSet) error {
func (store *reachabilityStore) serializeFutureCoveringSet(w io.Writer, futureCoveringSet futureCoveringBlockSet) error {
// Serialize the set size
err := domainmessage.WriteVarInt(w, uint64(len(futureCoveringSet)))
err := wire.WriteVarInt(w, uint64(len(futureCoveringSet)))
if err != nil {
return err
}
// Serialize each node in the set
for _, node := range futureCoveringSet {
err = domainmessage.WriteElement(w, node.blockNode.hash)
// Serialize each block in the set
for _, block := range futureCoveringSet {
err = wire.WriteElement(w, block.blockNode.hash)
if err != nil {
return err
}
@@ -309,10 +308,17 @@ func (store *reachabilityStore) deserializeTreeNode(r io.Reader, destination *re
}
destination.treeNode.interval = interval
// Deserialize the remaining interval
remainingInterval, err := store.deserializeReachabilityInterval(r)
if err != nil {
return err
}
destination.treeNode.remainingInterval = remainingInterval
// Deserialize the parent
// If this is the zero hash, this node is the genesis and as such doesn't have a parent
parentHash := &daghash.Hash{}
err = domainmessage.ReadElement(r, parentHash)
err = wire.ReadElement(r, parentHash)
if err != nil {
return err
}
@@ -325,7 +331,7 @@ func (store *reachabilityStore) deserializeTreeNode(r io.Reader, destination *re
}
// Deserialize the amount of children
childCount, err := domainmessage.ReadVarInt(r)
childCount, err := wire.ReadVarInt(r)
if err != nil {
return err
}
@@ -334,7 +340,7 @@ func (store *reachabilityStore) deserializeTreeNode(r io.Reader, destination *re
children := make([]*reachabilityTreeNode, childCount)
for i := uint64(0); i < childCount; i++ {
childHash := &daghash.Hash{}
err = domainmessage.ReadElement(r, childHash)
err = wire.ReadElement(r, childHash)
if err != nil {
return err
}
@@ -354,7 +360,7 @@ func (store *reachabilityStore) deserializeReachabilityInterval(r io.Reader) (*r
// Deserialize start
start := uint64(0)
err := domainmessage.ReadElement(r, &start)
err := wire.ReadElement(r, &start)
if err != nil {
return nil, err
}
@@ -362,7 +368,7 @@ func (store *reachabilityStore) deserializeReachabilityInterval(r io.Reader) (*r
// Deserialize end
end := uint64(0)
err = domainmessage.ReadElement(r, &end)
err = wire.ReadElement(r, &end)
if err != nil {
return nil, err
}
@@ -373,24 +379,31 @@ func (store *reachabilityStore) deserializeReachabilityInterval(r io.Reader) (*r
func (store *reachabilityStore) deserializeFutureCoveringSet(r io.Reader, destination *reachabilityData) error {
// Deserialize the set size
setSize, err := domainmessage.ReadVarInt(r)
setSize, err := wire.ReadVarInt(r)
if err != nil {
return err
}
// Deserialize each block in the set
futureCoveringSet := make(futureCoveringTreeNodeSet, setSize)
futureCoveringSet := make(futureCoveringBlockSet, setSize)
for i := uint64(0); i < setSize; i++ {
blockHash := &daghash.Hash{}
err = domainmessage.ReadElement(r, blockHash)
err = wire.ReadElement(r, blockHash)
if err != nil {
return err
}
blockNode := store.dag.index.LookupNode(blockHash)
if blockNode == nil {
return errors.Errorf("blockNode not found for hash %s", blockHash)
}
blockReachabilityData, ok := store.reachabilityDataByHash(blockHash)
if !ok {
return errors.Errorf("block reachability data not found for hash: %s", blockHash)
}
futureCoveringSet[i] = blockReachabilityData.treeNode
futureCoveringSet[i] = &futureCoveringBlock{
blockNode: blockNode,
treeNode: blockReachabilityData.treeNode,
}
}
destination.futureCoveringSet = futureCoveringSet

View File

@@ -9,15 +9,15 @@ import (
"runtime"
"time"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/wire"
)
// txValidateItem holds a transaction along with which input to validate.
type txValidateItem struct {
txInIndex int
txIn *domainmessage.TxIn
txIn *wire.TxIn
tx *util.Tx
}
@@ -126,7 +126,7 @@ func (v *txValidator) Validate(items []*txValidateItem) error {
// Start up validation handlers that are used to asynchronously
// validate each transaction input.
for i := 0; i < maxGoRoutines; i++ {
spawn("txValidator.validateHandler", v.validateHandler)
spawn(v.validateHandler)
}
// Validate each of the inputs. The quit channel is closed when any
@@ -179,6 +179,11 @@ func newTxValidator(utxoSet UTXOSet, flags txscript.ScriptFlags, sigCache *txscr
// ValidateTransactionScripts validates the scripts for the passed transaction
// using multiple goroutines.
func ValidateTransactionScripts(tx *util.Tx, utxoSet UTXOSet, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
// Don't validate coinbase transaction scripts.
if tx.IsCoinBase() {
return nil
}
// Collect all of the transaction inputs and required information for
// validation.
txIns := tx.MsgTx().TxIn
@@ -208,6 +213,10 @@ func checkBlockScripts(block *blockNode, utxoSet UTXOSet, transactions []*util.T
}
txValItems := make([]*txValidateItem, 0, numInputs)
for _, tx := range transactions {
// Skip coinbase transactions.
if tx.IsCoinBase() {
continue
}
for txInIdx, txIn := range tx.MsgTx().TxIn {
txVI := &txValidateItem{
txInIndex: txInIdx,

View File

@@ -10,7 +10,7 @@ import (
"runtime"
"testing"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/txscript"
)
// TestCheckBlockScripts ensures that validating the all of the scripts in a

View File

@@ -4,14 +4,13 @@ import (
"bytes"
"encoding/binary"
"fmt"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
)
// registerSubnetworks scans a list of transactions, singles out
@@ -19,7 +18,7 @@ import (
// subnetwork based on it.
// This function returns an error if one or more transactions are invalid
func registerSubnetworks(dbContext dbaccess.Context, txs []*util.Tx) error {
subnetworkRegistryTxs := make([]*domainmessage.MsgTx, 0)
subnetworkRegistryTxs := make([]*wire.MsgTx, 0)
for _, tx := range txs {
msgTx := tx.MsgTx()
@@ -60,7 +59,7 @@ func registerSubnetworks(dbContext dbaccess.Context, txs []*util.Tx) error {
// validateSubnetworkRegistryTransaction makes sure that a given subnetwork registry
// transaction is valid. Such a transaction is valid iff:
// - Its entire payload is a uint64 (8 bytes)
func validateSubnetworkRegistryTransaction(tx *domainmessage.MsgTx) error {
func validateSubnetworkRegistryTransaction(tx *wire.MsgTx) error {
if len(tx.Payload) != 8 {
return ruleError(ErrSubnetworkRegistry, fmt.Sprintf("validation failed: subnetwork registry"+
"tx '%s' has an invalid payload", tx.TxHash()))
@@ -70,14 +69,14 @@ func validateSubnetworkRegistryTransaction(tx *domainmessage.MsgTx) error {
}
// TxToSubnetworkID creates a subnetwork ID from a subnetwork registry transaction
func TxToSubnetworkID(tx *domainmessage.MsgTx) (*subnetworkid.SubnetworkID, error) {
func TxToSubnetworkID(tx *wire.MsgTx) (*subnetworkid.SubnetworkID, error) {
txHash := tx.TxHash()
return subnetworkid.New(util.Hash160(txHash[:]))
}
// fetchSubnetwork returns a registered subnetwork.
func (dag *BlockDAG) fetchSubnetwork(subnetworkID *subnetworkid.SubnetworkID) (*subnetwork, error) {
serializedSubnetwork, err := dbaccess.FetchSubnetworkData(dag.databaseContext, subnetworkID)
func fetchSubnetwork(subnetworkID *subnetworkid.SubnetworkID) (*subnetwork, error) {
serializedSubnetwork, err := dbaccess.FetchSubnetworkData(dbaccess.NoTx(), subnetworkID)
if err != nil {
return nil, err
}
@@ -92,8 +91,8 @@ func (dag *BlockDAG) fetchSubnetwork(subnetworkID *subnetworkid.SubnetworkID) (*
// GasLimit returns the gas limit of a registered subnetwork. If the subnetwork does not
// exist this method returns an error.
func (dag *BlockDAG) GasLimit(subnetworkID *subnetworkid.SubnetworkID) (uint64, error) {
sNet, err := dag.fetchSubnetwork(subnetworkID)
func GasLimit(subnetworkID *subnetworkid.SubnetworkID) (uint64, error) {
sNet, err := fetchSubnetwork(subnetworkID)
if err != nil {
return 0, err
}
@@ -114,14 +113,14 @@ type subnetwork struct {
gasLimit uint64
}
func newSubnetwork(tx *domainmessage.MsgTx) *subnetwork {
func newSubnetwork(tx *wire.MsgTx) *subnetwork {
return &subnetwork{
gasLimit: ExtractGasLimit(tx),
}
}
// ExtractGasLimit extracts the gas limit from the transaction payload
func ExtractGasLimit(tx *domainmessage.MsgTx) uint64 {
func ExtractGasLimit(tx *wire.MsgTx) uint64 {
return binary.LittleEndian.Uint64(tx.Payload[:8])
}
@@ -155,50 +154,3 @@ func deserializeSubnetwork(serializedSNetBytes []byte) (*subnetwork, error) {
gasLimit: gasLimit,
}, nil
}
func (dag *BlockDAG) validateGasLimit(block *util.Block) error {
var currentSubnetworkID *subnetworkid.SubnetworkID
var currentSubnetworkGasLimit uint64
var currentGasUsage uint64
var err error
// We assume here that transactions are ordered by subnetworkID,
// since it was already validated in checkTransactionSanity
for _, tx := range block.Transactions() {
msgTx := tx.MsgTx()
// In native and Built-In subnetworks all txs must have Gas = 0, and that was already validated in checkTransactionSanity
// Therefore - no need to check them here.
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) || msgTx.SubnetworkID.IsBuiltIn() {
continue
}
if !msgTx.SubnetworkID.IsEqual(currentSubnetworkID) {
currentSubnetworkID = &msgTx.SubnetworkID
currentGasUsage = 0
currentSubnetworkGasLimit, err = dag.GasLimit(currentSubnetworkID)
if err != nil {
return errors.Errorf("Error getting gas limit for subnetworkID '%s': %s", currentSubnetworkID, err)
}
}
newGasUsage := currentGasUsage + msgTx.Gas
if newGasUsage < currentGasUsage { // check for overflow
str := fmt.Sprintf("Block gas usage in subnetwork with ID %s has overflown", currentSubnetworkID)
return ruleError(ErrInvalidGas, str)
}
if newGasUsage > currentSubnetworkGasLimit {
str := fmt.Sprintf("Block wastes too much gas in subnetwork with ID %s", currentSubnetworkID)
return ruleError(ErrInvalidGas, str)
}
currentGasUsage = newGasUsage
}
return nil
}
// SubnetworkID returns the node's subnetwork ID
func (dag *BlockDAG) SubnetworkID() *subnetworkid.SubnetworkID {
return dag.subnetworkID
}

57
blockdag/sync_rate.go Normal file
View File

@@ -0,0 +1,57 @@
package blockdag
import "time"
const syncRateWindowDuration = 15 * time.Minute
// addBlockProcessingTimestamp adds the last block processing timestamp in order to measure the recent sync rate.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) addBlockProcessingTimestamp() {
now := time.Now()
dag.recentBlockProcessingTimestamps = append(dag.recentBlockProcessingTimestamps, now)
dag.removeNonRecentTimestampsFromRecentBlockProcessingTimestamps()
}
// removeNonRecentTimestampsFromRecentBlockProcessingTimestamps removes timestamps older than syncRateWindowDuration
// from dag.recentBlockProcessingTimestamps
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) removeNonRecentTimestampsFromRecentBlockProcessingTimestamps() {
dag.recentBlockProcessingTimestamps = dag.recentBlockProcessingTimestampsRelevantWindow()
}
func (dag *BlockDAG) recentBlockProcessingTimestampsRelevantWindow() []time.Time {
minTime := time.Now().Add(-syncRateWindowDuration)
windowStartIndex := len(dag.recentBlockProcessingTimestamps)
for i, processTime := range dag.recentBlockProcessingTimestamps {
if processTime.After(minTime) {
windowStartIndex = i
break
}
}
return dag.recentBlockProcessingTimestamps[windowStartIndex:]
}
// syncRate returns the rate of processed
// blocks in the last syncRateWindowDuration
// duration.
func (dag *BlockDAG) syncRate() float64 {
dag.RLock()
defer dag.RUnlock()
return float64(len(dag.recentBlockProcessingTimestampsRelevantWindow())) / syncRateWindowDuration.Seconds()
}
// IsSyncRateBelowThreshold checks whether the sync rate
// is below the expected threshold.
func (dag *BlockDAG) IsSyncRateBelowThreshold(maxDeviation float64) bool {
if dag.uptime() < syncRateWindowDuration {
return false
}
return dag.syncRate() < 1/dag.dagParams.TargetTimePerBlock.Seconds()*maxDeviation
}
func (dag *BlockDAG) uptime() time.Duration {
return time.Now().Sub(dag.startTime)
}

View File

@@ -5,6 +5,9 @@ package blockdag
import (
"compress/bzip2"
"encoding/binary"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"io"
"io/ioutil"
"os"
@@ -14,17 +17,11 @@ import (
"sync"
"testing"
"github.com/kaspanet/kaspad/infrastructure/database/ffldb/ldb"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
)
// FileExists returns whether or not the named file or directory exists.
@@ -50,9 +47,9 @@ func DAGSetup(dbName string, openDb bool, config Config) (*BlockDAG, func(), err
// overwrite `spawn` to count the number of running goroutines
spawnWaitGroup := sync.WaitGroup{}
realSpawn := spawn
spawn = func(name string, f func()) {
spawn = func(f func()) {
spawnWaitGroup.Add(1)
realSpawn(name, func() {
realSpawn(func() {
f()
spawnWaitGroup.Done()
})
@@ -65,31 +62,19 @@ func DAGSetup(dbName string, openDb bool, config Config) (*BlockDAG, func(), err
return nil, nil, errors.Errorf("error creating temp dir: %s", err)
}
// We set ldb.Options here to return nil because normally
// the database is initialized with very large caches that
// can make opening/closing the database for every test
// quite heavy.
originalLDBOptions := ldb.Options
ldb.Options = func() *opt.Options {
return nil
}
dbPath := filepath.Join(tmpDir, dbName)
_ = os.RemoveAll(dbPath)
databaseContext, err := dbaccess.New(dbPath)
err = dbaccess.Open(dbPath)
if err != nil {
return nil, nil, errors.Errorf("error creating db: %s", err)
}
config.DatabaseContext = databaseContext
// Setup a teardown function for cleaning up. This function is
// returned to the caller to be invoked when it is done testing.
teardown = func() {
spawnWaitGroup.Wait()
spawn = realSpawn
databaseContext.Close()
ldb.Options = originalLDBOptions
dbaccess.Close()
os.RemoveAll(dbPath)
}
} else {
@@ -121,30 +106,30 @@ type txSubnetworkData struct {
Payload []byte
}
func createTxForTest(numInputs uint32, numOutputs uint32, outputValue uint64, subnetworkData *txSubnetworkData) *domainmessage.MsgTx {
txIns := []*domainmessage.TxIn{}
txOuts := []*domainmessage.TxOut{}
func createTxForTest(numInputs uint32, numOutputs uint32, outputValue uint64, subnetworkData *txSubnetworkData) *wire.MsgTx {
txIns := []*wire.TxIn{}
txOuts := []*wire.TxOut{}
for i := uint32(0); i < numInputs; i++ {
txIns = append(txIns, &domainmessage.TxIn{
PreviousOutpoint: *domainmessage.NewOutpoint(&daghash.TxID{}, i),
txIns = append(txIns, &wire.TxIn{
PreviousOutpoint: *wire.NewOutpoint(&daghash.TxID{}, i),
SignatureScript: []byte{},
Sequence: domainmessage.MaxTxInSequenceNum,
Sequence: wire.MaxTxInSequenceNum,
})
}
for i := uint32(0); i < numOutputs; i++ {
txOuts = append(txOuts, &domainmessage.TxOut{
txOuts = append(txOuts, &wire.TxOut{
ScriptPubKey: OpTrueScript,
Value: outputValue,
})
}
if subnetworkData != nil {
return domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, txIns, txOuts, subnetworkData.subnetworkID, subnetworkData.Gas, subnetworkData.Payload)
return wire.NewSubnetworkMsgTx(wire.TxVersion, txIns, txOuts, subnetworkData.subnetworkID, subnetworkData.Gas, subnetworkData.Payload)
}
return domainmessage.NewNativeMsgTx(domainmessage.TxVersion, txIns, txOuts)
return wire.NewNativeMsgTx(wire.TxVersion, txIns, txOuts)
}
// VirtualForTest is an exported version for virtualBlock, so that it can be returned by exported test_util methods
@@ -161,8 +146,8 @@ func SetVirtualForTest(dag *BlockDAG, virtual VirtualForTest) VirtualForTest {
func GetVirtualFromParentsForTest(dag *BlockDAG, parentHashes []*daghash.Hash) (VirtualForTest, error) {
parents := newBlockSet()
for _, hash := range parentHashes {
parent, ok := dag.index.LookupNode(hash)
if !ok {
parent := dag.index.LookupNode(hash)
if parent == nil {
return nil, errors.Errorf("GetVirtualFromParentsForTest: didn't found node for hash %s", hash)
}
parents.add(parent)
@@ -186,7 +171,7 @@ func GetVirtualFromParentsForTest(dag *BlockDAG, parentHashes []*daghash.Hash) (
// LoadBlocks reads files containing kaspa gzipped block data from disk
// and returns them as an array of util.Block.
func LoadBlocks(filename string) (blocks []*util.Block, err error) {
var network = domainmessage.Mainnet
var network = wire.Mainnet
var dr io.Reader
var fi io.ReadCloser
@@ -244,32 +229,15 @@ func opTrueAddress(prefix util.Bech32Prefix) (util.Address, error) {
}
// PrepareBlockForTest generates a block with the proper merkle roots, coinbase transaction etc. This function is used for test purposes only
func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactions []*domainmessage.MsgTx) (*domainmessage.MsgBlock, error) {
parents := newBlockSet()
for _, hash := range parentHashes {
parent, ok := dag.index.LookupNode(hash)
if !ok {
return nil, errors.Errorf("parent %s was not found", hash)
}
parents.add(parent)
}
node, _ := dag.newBlockNode(nil, parents)
_, selectedParentPastUTXO, txsAcceptanceData, err := dag.pastUTXO(node)
func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactions []*wire.MsgTx) (*wire.MsgBlock, error) {
newVirtual, err := GetVirtualFromParentsForTest(dag, parentHashes)
if err != nil {
return nil, err
}
oldVirtual := SetVirtualForTest(dag, newVirtual)
defer SetVirtualForTest(dag, oldVirtual)
calculatedAccepetedIDMerkleRoot := calculateAcceptedIDMerkleRoot(txsAcceptanceData)
multiset, err := node.calcMultiset(dag, txsAcceptanceData, selectedParentPastUTXO)
if err != nil {
return nil, err
}
calculatedMultisetHash := daghash.Hash(*multiset.Finalize())
OpTrueAddr, err := opTrueAddress(dag.Params.Prefix)
OpTrueAddr, err := opTrueAddress(dag.dagParams.Prefix)
if err != nil {
return nil, err
}
@@ -282,12 +250,7 @@ func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactio
return nil, err
}
coinbasePayloadScriptPubKey, err := txscript.PayToAddrScript(OpTrueAddr)
if err != nil {
return nil, err
}
blockTransactions[0], err = node.expectedCoinbaseTransaction(dag, txsAcceptanceData, coinbasePayloadScriptPubKey, coinbasePayloadExtraData)
blockTransactions[0], err = dag.NextCoinbaseFromAddress(OpTrueAddr, coinbasePayloadExtraData)
if err != nil {
return nil, err
}
@@ -307,33 +270,19 @@ func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactio
return subnetworkid.Less(&blockTransactions[i].MsgTx().SubnetworkID, &blockTransactions[j].MsgTx().SubnetworkID)
})
// Create a new block ready to be solved.
hashMerkleTree := BuildHashMerkleTreeStore(blockTransactions)
var msgBlock domainmessage.MsgBlock
for _, tx := range blockTransactions {
msgBlock.AddTransaction(tx.MsgTx())
block, err := dag.BlockForMining(blockTransactions)
if err != nil {
return nil, err
}
block.Header.Timestamp = dag.NextBlockMinimumTime()
block.Header.Bits = dag.NextRequiredDifficulty(block.Header.Timestamp)
timestamp := node.parents.bluest().PastMedianTime(dag)
msgBlock.Header = domainmessage.BlockHeader{
Version: blockVersion,
// We use parents.hashes() and not parentHashes because parents.hashes() is sorted.
ParentHashes: parents.hashes(),
HashMerkleRoot: hashMerkleTree.Root(),
AcceptedIDMerkleRoot: calculatedAccepetedIDMerkleRoot,
UTXOCommitment: &calculatedMultisetHash,
Timestamp: timestamp,
Bits: dag.requiredDifficulty(node.parents.bluest(), timestamp),
}
return &msgBlock, nil
return block, nil
}
// PrepareAndProcessBlockForTest prepares a block that points to the given parent
// hashes and process it.
func PrepareAndProcessBlockForTest(t *testing.T, dag *BlockDAG, parentHashes []*daghash.Hash, transactions []*domainmessage.MsgTx) *domainmessage.MsgBlock {
func PrepareAndProcessBlockForTest(t *testing.T, dag *BlockDAG, parentHashes []*daghash.Hash, transactions []*wire.MsgTx) *wire.MsgBlock {
daghash.Sort(parentHashes)
block, err := PrepareBlockForTest(dag, parentHashes, transactions)
if err != nil {

BIN
blockdag/testdata/blk_0_to_4.dat vendored Normal file

Binary file not shown.

BIN
blockdag/testdata/blk_3A.dat vendored Normal file

Binary file not shown.

BIN
blockdag/testdata/blk_3B.dat vendored Normal file

Binary file not shown.

BIN
blockdag/testdata/blk_3C.dat vendored Normal file

Binary file not shown.

BIN
blockdag/testdata/blk_3D.dat vendored Normal file

Binary file not shown.

356
blockdag/thresholdstate.go Normal file
View File

@@ -0,0 +1,356 @@
// Copyright (c) 2016-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/util/daghash"
"github.com/pkg/errors"
)
// ThresholdState define the various threshold states used when voting on
// consensus changes.
type ThresholdState byte
// These constants are used to identify specific threshold states.
const (
// ThresholdDefined is the first state for each deployment and is the
// state for the genesis block has by definition for all deployments.
ThresholdDefined ThresholdState = iota
// ThresholdStarted is the state for a deployment once its start time
// has been reached.
ThresholdStarted
// ThresholdLockedIn is the state for a deployment during the retarget
// period which is after the ThresholdStarted state period and the
// number of blocks that have voted for the deployment equal or exceed
// the required number of votes for the deployment.
ThresholdLockedIn
// ThresholdActive is the state for a deployment for all blocks after a
// retarget period in which the deployment was in the ThresholdLockedIn
// state.
ThresholdActive
// ThresholdFailed is the state for a deployment once its expiration
// time has been reached and it did not reach the ThresholdLockedIn
// state.
ThresholdFailed
// numThresholdsStates is the maximum number of threshold states used in
// tests.
numThresholdsStates
)
// thresholdStateStrings is a map of ThresholdState values back to their
// constant names for pretty printing.
var thresholdStateStrings = map[ThresholdState]string{
ThresholdDefined: "ThresholdDefined",
ThresholdStarted: "ThresholdStarted",
ThresholdLockedIn: "ThresholdLockedIn",
ThresholdActive: "ThresholdActive",
ThresholdFailed: "ThresholdFailed",
}
// String returns the ThresholdState as a human-readable name.
func (t ThresholdState) String() string {
if s := thresholdStateStrings[t]; s != "" {
return s
}
return fmt.Sprintf("Unknown ThresholdState (%d)", int(t))
}
// thresholdConditionChecker provides a generic interface that is invoked to
// determine when a consensus rule change threshold should be changed.
type thresholdConditionChecker interface {
// BeginTime returns the unix timestamp for the median block time after
// which voting on a rule change starts (at the next window).
BeginTime() uint64
// EndTime returns the unix timestamp for the median block time after
// which an attempted rule change fails if it has not already been
// locked in or activated.
EndTime() uint64
// RuleChangeActivationThreshold is the number of blocks for which the
// condition must be true in order to lock in a rule change.
RuleChangeActivationThreshold() uint64
// MinerConfirmationWindow is the number of blocks in each threshold
// state retarget window.
MinerConfirmationWindow() uint64
// Condition returns whether or not the rule change activation condition
// has been met. This typically involves checking whether or not the
// bit associated with the condition is set, but can be more complex as
// needed.
Condition(*blockNode) (bool, error)
}
// thresholdStateCache provides a type to cache the threshold states of each
// threshold window for a set of IDs.
type thresholdStateCache struct {
entries map[daghash.Hash]ThresholdState
}
// Lookup returns the threshold state associated with the given hash along with
// a boolean that indicates whether or not it is valid.
func (c *thresholdStateCache) Lookup(hash *daghash.Hash) (ThresholdState, bool) {
state, ok := c.entries[*hash]
return state, ok
}
// Update updates the cache to contain the provided hash to threshold state
// mapping.
func (c *thresholdStateCache) Update(hash *daghash.Hash, state ThresholdState) {
c.entries[*hash] = state
}
// newThresholdCaches returns a new array of caches to be used when calculating
// threshold states.
func newThresholdCaches(numCaches uint32) []thresholdStateCache {
caches := make([]thresholdStateCache, numCaches)
for i := 0; i < len(caches); i++ {
caches[i] = thresholdStateCache{
entries: make(map[daghash.Hash]ThresholdState),
}
}
return caches
}
// thresholdState returns the current rule change threshold state for the block
// AFTER the given node and deployment ID. The cache is used to ensure the
// threshold states for previous windows are only calculated once.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) thresholdState(prevNode *blockNode, checker thresholdConditionChecker, cache *thresholdStateCache) (ThresholdState, error) {
// The threshold state for the window that contains the genesis block is
// defined by definition.
confirmationWindow := checker.MinerConfirmationWindow()
if prevNode == nil || (prevNode.blueScore+1) < confirmationWindow {
return ThresholdDefined, nil
}
// Get the ancestor that is the last block of the previous confirmation
// window in order to get its threshold state. This can be done because
// the state is the same for all blocks within a given window.
prevNode = prevNode.SelectedAncestor(prevNode.blueScore -
(prevNode.blueScore+1)%confirmationWindow)
// Iterate backwards through each of the previous confirmation windows
// to find the most recently cached threshold state.
var neededStates []*blockNode
for prevNode != nil {
// Nothing more to do if the state of the block is already
// cached.
if _, ok := cache.Lookup(prevNode.hash); ok {
break
}
// The start and expiration times are based on the median block
// time, so calculate it now.
medianTime := prevNode.PastMedianTime(dag)
// The state is simply defined if the start time hasn't been
// been reached yet.
if uint64(medianTime.Unix()) < checker.BeginTime() {
cache.Update(prevNode.hash, ThresholdDefined)
break
}
// Add this node to the list of nodes that need the state
// calculated and cached.
neededStates = append(neededStates, prevNode)
// Get the ancestor that is the last block of the previous
// confirmation window.
prevNode = prevNode.RelativeAncestor(confirmationWindow)
}
// Start with the threshold state for the most recent confirmation
// window that has a cached state.
state := ThresholdDefined
if prevNode != nil {
var ok bool
state, ok = cache.Lookup(prevNode.hash)
if !ok {
return ThresholdFailed, errors.Errorf(
"thresholdState: cache lookup failed for %s",
prevNode.hash)
}
}
// Since each threshold state depends on the state of the previous
// window, iterate starting from the oldest unknown window.
for neededNum := len(neededStates) - 1; neededNum >= 0; neededNum-- {
prevNode := neededStates[neededNum]
switch state {
case ThresholdDefined:
// The deployment of the rule change fails if it expires
// before it is accepted and locked in.
medianTime := prevNode.PastMedianTime(dag)
medianTimeUnix := uint64(medianTime.Unix())
if medianTimeUnix >= checker.EndTime() {
state = ThresholdFailed
break
}
// The state for the rule moves to the started state
// once its start time has been reached (and it hasn't
// already expired per the above).
if medianTimeUnix >= checker.BeginTime() {
state = ThresholdStarted
}
case ThresholdStarted:
// The deployment of the rule change fails if it expires
// before it is accepted and locked in.
medianTime := prevNode.PastMedianTime(dag)
if uint64(medianTime.Unix()) >= checker.EndTime() {
state = ThresholdFailed
break
}
// At this point, the rule change is still being voted
// on by the miners, so iterate backwards through the
// confirmation window to count all of the votes in it.
var count uint64
windowNodes := make([]*blockNode, 0, confirmationWindow)
windowNodes = append(windowNodes, prevNode)
windowNodes = append(windowNodes, blueBlockWindow(prevNode, confirmationWindow-1)...)
for _, current := range windowNodes {
condition, err := checker.Condition(current)
if err != nil {
return ThresholdFailed, err
}
if condition {
count++
}
}
// The state is locked in if the number of blocks in the
// period that voted for the rule change meets the
// activation threshold.
if count >= checker.RuleChangeActivationThreshold() {
state = ThresholdLockedIn
}
case ThresholdLockedIn:
// The new rule becomes active when its previous state
// was locked in.
state = ThresholdActive
// Nothing to do if the previous state is active or failed since
// they are both terminal states.
case ThresholdActive:
case ThresholdFailed:
}
// Update the cache to avoid recalculating the state in the
// future.
cache.Update(prevNode.hash, state)
}
return state, nil
}
// ThresholdState returns the current rule change threshold state of the given
// deployment ID for the block AFTER the blueScore of the current DAG.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) ThresholdState(deploymentID uint32) (ThresholdState, error) {
dag.dagLock.Lock()
defer dag.dagLock.Unlock()
state, err := dag.deploymentState(dag.selectedTip(), deploymentID)
return state, err
}
// IsDeploymentActive returns true if the target deploymentID is active, and
// false otherwise.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) IsDeploymentActive(deploymentID uint32) (bool, error) {
dag.dagLock.Lock()
defer dag.dagLock.Unlock()
state, err := dag.deploymentState(dag.selectedTip(), deploymentID)
if err != nil {
return false, err
}
return state == ThresholdActive, nil
}
// deploymentState returns the current rule change threshold for a given
// deploymentID. The threshold is evaluated from the point of view of the block
// node passed in as the first argument to this method.
//
// It is important to note that, as the variable name indicates, this function
// expects the block node prior to the block for which the deployment state is
// desired. In other words, the returned deployment state is for the block
// AFTER the passed node.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) deploymentState(prevNode *blockNode, deploymentID uint32) (ThresholdState, error) {
if deploymentID > uint32(len(dag.dagParams.Deployments)) {
return ThresholdFailed, errors.Errorf("deployment ID %d does not exist", deploymentID)
}
deployment := &dag.dagParams.Deployments[deploymentID]
checker := deploymentChecker{deployment: deployment, dag: dag}
cache := &dag.deploymentCaches[deploymentID]
return dag.thresholdState(prevNode, checker, cache)
}
// initThresholdCaches initializes the threshold state caches for each warning
// bit and defined deployment and provides warnings if the DAG is current per
// the warnUnknownVersions and warnUnknownRuleActivations functions.
func (dag *BlockDAG) initThresholdCaches() error {
// Initialize the warning and deployment caches by calculating the
// threshold state for each of them. This will ensure the caches are
// populated and any states that needed to be recalculated due to
// definition changes is done now.
prevNode := dag.selectedTip().selectedParent
for bit := uint32(0); bit < vbNumBits; bit++ {
checker := bitConditionChecker{bit: bit, dag: dag}
cache := &dag.warningCaches[bit]
_, err := dag.thresholdState(prevNode, checker, cache)
if err != nil {
return err
}
}
for id := 0; id < len(dag.dagParams.Deployments); id++ {
deployment := &dag.dagParams.Deployments[id]
cache := &dag.deploymentCaches[id]
checker := deploymentChecker{deployment: deployment, dag: dag}
_, err := dag.thresholdState(prevNode, checker, cache)
if err != nil {
return err
}
}
// No warnings about unknown rules or versions until the DAG is
// current.
if dag.isCurrent() {
// Warn if a high enough percentage of the last blocks have
// unexpected versions.
bestNode := dag.selectedTip()
if err := dag.warnUnknownVersions(bestNode); err != nil {
return err
}
// Warn if any unknown new rules are either about to activate or
// have already been activated.
if err := dag.warnUnknownRuleActivations(bestNode); err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,134 @@
// Copyright (c) 2016 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 (
"testing"
"github.com/kaspanet/kaspad/util/daghash"
)
// TestThresholdStateStringer tests the stringized output for the
// ThresholdState type.
func TestThresholdStateStringer(t *testing.T) {
t.Parallel()
tests := []struct {
in ThresholdState
want string
}{
{ThresholdDefined, "ThresholdDefined"},
{ThresholdStarted, "ThresholdStarted"},
{ThresholdLockedIn, "ThresholdLockedIn"},
{ThresholdActive, "ThresholdActive"},
{ThresholdFailed, "ThresholdFailed"},
{0xff, "Unknown ThresholdState (255)"},
}
// Detect additional threshold states that don't have the stringer added.
if len(tests)-1 != int(numThresholdsStates) {
t.Errorf("It appears a threshold statewas added without " +
"adding an associated stringer test")
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
result := test.in.String()
if result != test.want {
t.Errorf("String #%d\n got: %s want: %s", i, result,
test.want)
continue
}
}
}
// TestThresholdStateCache ensure the threshold state cache works as intended
// including adding entries, updating existing entries, and flushing.
func TestThresholdStateCache(t *testing.T) {
t.Parallel()
tests := []struct {
name string
numEntries int
state ThresholdState
}{
{name: "2 entries defined", numEntries: 2, state: ThresholdDefined},
{name: "7 entries started", numEntries: 7, state: ThresholdStarted},
{name: "10 entries active", numEntries: 10, state: ThresholdActive},
{name: "5 entries locked in", numEntries: 5, state: ThresholdLockedIn},
{name: "3 entries failed", numEntries: 3, state: ThresholdFailed},
}
nextTest:
for _, test := range tests {
cache := &newThresholdCaches(1)[0]
for i := 0; i < test.numEntries; i++ {
var hash daghash.Hash
hash[0] = uint8(i + 1)
// Ensure the hash isn't available in the cache already.
_, ok := cache.Lookup(&hash)
if ok {
t.Errorf("Lookup (%s): has entry for hash %v",
test.name, hash)
continue nextTest
}
// Ensure hash that was added to the cache reports it's
// available and the state is the expected value.
cache.Update(&hash, test.state)
state, ok := cache.Lookup(&hash)
if !ok {
t.Errorf("Lookup (%s): missing entry for hash "+
"%v", test.name, hash)
continue nextTest
}
if state != test.state {
t.Errorf("Lookup (%s): state mismatch - got "+
"%v, want %v", test.name, state,
test.state)
continue nextTest
}
// Ensure adding an existing hash with the same state
// doesn't break the existing entry.
cache.Update(&hash, test.state)
state, ok = cache.Lookup(&hash)
if !ok {
t.Errorf("Lookup (%s): missing entry after "+
"second add for hash %v", test.name,
hash)
continue nextTest
}
if state != test.state {
t.Errorf("Lookup (%s): state mismatch after "+
"second add - got %v, want %v",
test.name, state, test.state)
continue nextTest
}
// Ensure adding an existing hash with a different state
// updates the existing entry.
newState := ThresholdFailed
if newState == test.state {
newState = ThresholdStarted
}
cache.Update(&hash, newState)
state, ok = cache.Lookup(&hash)
if !ok {
t.Errorf("Lookup (%s): missing entry after "+
"state change for hash %v", test.name,
hash)
continue nextTest
}
if state != newState {
t.Errorf("Lookup (%s): state mismatch after "+
"state change - got %v, want %v",
test.name, state, newState)
continue nextTest
}
}
}
}

View File

@@ -1,22 +1,22 @@
package blockdag
import (
"github.com/kaspanet/kaspad/util/mstime"
"time"
)
// TimeSource is the interface to access time.
type TimeSource interface {
// Now returns the current time.
Now() mstime.Time
Now() time.Time
}
// timeSource provides an implementation of the TimeSource interface
// that simply returns the current local time.
type timeSource struct{}
// Now returns the current local time, with one millisecond precision.
func (m *timeSource) Now() mstime.Time {
return mstime.Now()
// Now returns the current local time, with one second precision.
func (m *timeSource) Now() time.Time {
return time.Unix(time.Now().Unix(), 0)
}
// NewTimeSource returns a new instance of a TimeSource

View File

@@ -3,10 +3,10 @@ package blockdag
import (
"bytes"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/wire"
)
func addUTXOToMultiset(ms *secp256k1.MultiSet, entry *UTXOEntry, outpoint *domainmessage.Outpoint) (*secp256k1.MultiSet, error) {
func addUTXOToMultiset(ms *secp256k1.MultiSet, entry *UTXOEntry, outpoint *wire.Outpoint) (*secp256k1.MultiSet, error) {
w := &bytes.Buffer{}
err := serializeUTXO(w, entry, outpoint)
if err != nil {
@@ -16,7 +16,7 @@ func addUTXOToMultiset(ms *secp256k1.MultiSet, entry *UTXOEntry, outpoint *domai
return ms, nil
}
func removeUTXOFromMultiset(ms *secp256k1.MultiSet, entry *UTXOEntry, outpoint *domainmessage.Outpoint) (*secp256k1.MultiSet, error) {
func removeUTXOFromMultiset(ms *secp256k1.MultiSet, entry *UTXOEntry, outpoint *wire.Outpoint) (*secp256k1.MultiSet, error) {
w := &bytes.Buffer{}
err := serializeUTXO(w, entry, outpoint)
if err != nil {

View File

@@ -2,8 +2,7 @@ package blockdag
import (
"bytes"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/locks"
)
@@ -117,7 +116,7 @@ func (diffStore *utxoDiffStore) diffChildByNode(node *blockNode) (*blockNode, er
}
func (diffStore *utxoDiffStore) diffDataFromDB(hash *daghash.Hash) (*blockUTXODiffData, error) {
serializedBlockDiffData, err := dbaccess.FetchUTXODiffData(diffStore.dag.databaseContext, hash)
serializedBlockDiffData, err := dbaccess.FetchUTXODiffData(dbaccess.NoTx(), hash)
if err != nil {
return nil, err
}
@@ -157,24 +156,17 @@ func (diffStore *utxoDiffStore) clearDirtyEntries() {
var maxBlueScoreDifferenceToKeepLoaded uint64 = 100
// clearOldEntries removes entries whose blue score is lower than
// virtual.blueScore - maxBlueScoreDifferenceToKeepLoaded. Note
// that tips are not removed either even if their blue score is
// lower than the above.
// virtual.blueScore - maxBlueScoreDifferenceToKeepLoaded.
func (diffStore *utxoDiffStore) clearOldEntries() {
diffStore.mtx.HighPriorityWriteLock()
defer diffStore.mtx.HighPriorityWriteUnlock()
virtualBlueScore := diffStore.dag.VirtualBlueScore()
minBlueScore := virtualBlueScore - maxBlueScoreDifferenceToKeepLoaded
if maxBlueScoreDifferenceToKeepLoaded > virtualBlueScore {
minBlueScore = 0
}
tips := diffStore.dag.virtual.tips()
toRemove := make(map[*blockNode]struct{})
for node := range diffStore.loaded {
if node.blueScore < minBlueScore && !tips.contains(node) {
if node.blueScore < minBlueScore {
toRemove[node] = struct{}{}
}
}

View File

@@ -1,13 +1,12 @@
package blockdag
import (
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
"reflect"
"testing"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/util/daghash"
)
func TestUTXODiffStore(t *testing.T) {
@@ -42,8 +41,8 @@ func TestUTXODiffStore(t *testing.T) {
// Add node's diff data to the utxoDiffStore and check if it's checked correctly.
node := createNode()
diff := NewUTXODiff()
diff.toAdd.add(domainmessage.Outpoint{TxID: daghash.TxID{0x01}, Index: 0}, &UTXOEntry{amount: 1, scriptPubKey: []byte{0x01}})
diff.toRemove.add(domainmessage.Outpoint{TxID: daghash.TxID{0x02}, Index: 0}, &UTXOEntry{amount: 2, scriptPubKey: []byte{0x02}})
diff.toAdd.add(wire.Outpoint{TxID: daghash.TxID{0x01}, Index: 0}, &UTXOEntry{amount: 1, scriptPubKey: []byte{0x01}})
diff.toRemove.add(wire.Outpoint{TxID: daghash.TxID{0x02}, Index: 0}, &UTXOEntry{amount: 2, scriptPubKey: []byte{0x02}})
if err := dag.utxoDiffStore.setBlockDiff(node, diff); err != nil {
t.Fatalf("setBlockDiff: unexpected error: %s", err)
}
@@ -66,7 +65,7 @@ func TestUTXODiffStore(t *testing.T) {
// Flush changes to db, delete them from the dag.utxoDiffStore.loaded
// map, and check if the diff data is re-fetched from the database.
dbTx, err := dag.databaseContext.NewTx()
dbTx, err := dbaccess.NewTx()
if err != nil {
t.Fatalf("Failed to open database transaction: %s", err)
}
@@ -115,8 +114,8 @@ func TestClearOldEntries(t *testing.T) {
for i := 0; i < 10; i++ {
processedBlock := PrepareAndProcessBlockForTest(t, dag, dag.TipHashes(), nil)
node, ok := dag.index.LookupNode(processedBlock.BlockHash())
if !ok {
node := dag.index.LookupNode(processedBlock.BlockHash())
if node == nil {
t.Fatalf("TestClearOldEntries: missing blockNode for hash %s", processedBlock.BlockHash())
}
blockNodes[i] = node
@@ -145,16 +144,15 @@ func TestClearOldEntries(t *testing.T) {
// Add a block on top of the genesis to force the retrieval of all diffData
processedBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil)
node, ok := dag.index.LookupNode(processedBlock.BlockHash())
if !ok {
node := dag.index.LookupNode(processedBlock.BlockHash())
if node == nil {
t.Fatalf("TestClearOldEntries: missing blockNode for hash %s", processedBlock.BlockHash())
}
// Make sure that the child-of-genesis node is in the loaded set, since it
// is a tip.
_, ok = dag.utxoDiffStore.loaded[node]
if !ok {
t.Fatalf("TestClearOldEntries: diffData for node %s is not in the loaded set", node.hash)
// Make sure that the child-of-genesis node isn't in the loaded set
_, ok := dag.utxoDiffStore.loaded[node]
if ok {
t.Fatalf("TestClearOldEntries: diffData for node %s is in the loaded set", node.hash)
}
// Make sure that all the old nodes still do not exist in the loaded set

View File

@@ -2,9 +2,9 @@ package blockdag
import (
"bytes"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/util/binaryserializer"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
"github.com/pkg/errors"
"io"
)
@@ -17,12 +17,12 @@ import (
// diff | UTXODiff | The diff data's diff
func serializeBlockUTXODiffData(w io.Writer, diffData *blockUTXODiffData) error {
hasDiffChild := diffData.diffChild != nil
err := domainmessage.WriteElement(w, hasDiffChild)
err := wire.WriteElement(w, hasDiffChild)
if err != nil {
return err
}
if hasDiffChild {
err := domainmessage.WriteElement(w, diffData.diffChild.hash)
err := wire.WriteElement(w, diffData.diffChild.hash)
if err != nil {
return err
}
@@ -41,23 +41,18 @@ func (diffStore *utxoDiffStore) deserializeBlockUTXODiffData(serializedDiffData
r := bytes.NewBuffer(serializedDiffData)
var hasDiffChild bool
err := domainmessage.ReadElement(r, &hasDiffChild)
err := wire.ReadElement(r, &hasDiffChild)
if err != nil {
return nil, err
}
if hasDiffChild {
hash := &daghash.Hash{}
err := domainmessage.ReadElement(r, hash)
err := wire.ReadElement(r, hash)
if err != nil {
return nil, err
}
var ok bool
diffData.diffChild, ok = diffStore.dag.index.LookupNode(hash)
if !ok {
return nil, errors.Errorf("block %s does not exist in the DAG", hash)
}
diffData.diffChild = diffStore.dag.index.LookupNode(hash)
}
diffData.diff, err = deserializeUTXODiff(r)
@@ -86,7 +81,7 @@ func deserializeUTXODiff(r io.Reader) (*UTXODiff, error) {
}
func deserializeUTXOCollection(r io.Reader) (utxoCollection, error) {
count, err := domainmessage.ReadVarInt(r)
count, err := wire.ReadVarInt(r)
if err != nil {
return nil, err
}
@@ -101,7 +96,7 @@ func deserializeUTXOCollection(r io.Reader) (utxoCollection, error) {
return collection, nil
}
func deserializeUTXO(r io.Reader) (*UTXOEntry, *domainmessage.Outpoint, error) {
func deserializeUTXO(r io.Reader) (*UTXOEntry, *wire.Outpoint, error) {
outpoint, err := deserializeOutpoint(r)
if err != nil {
return nil, nil, err
@@ -134,7 +129,7 @@ func serializeUTXODiff(w io.Writer, diff *UTXODiff) error {
// the utxo entries and serializing them and their corresponding outpoint
// prefixed by a varint that indicates their size.
func serializeUTXOCollection(w io.Writer, collection utxoCollection) error {
err := domainmessage.WriteVarInt(w, uint64(len(collection)))
err := wire.WriteVarInt(w, uint64(len(collection)))
if err != nil {
return err
}
@@ -148,7 +143,7 @@ func serializeUTXOCollection(w io.Writer, collection utxoCollection) error {
}
// serializeUTXO serializes a utxo entry-outpoint pair
func serializeUTXO(w io.Writer, entry *UTXOEntry, outpoint *domainmessage.Outpoint) error {
func serializeUTXO(w io.Writer, entry *UTXOEntry, outpoint *wire.Outpoint) error {
err := serializeOutpoint(w, outpoint)
if err != nil {
return err
@@ -163,7 +158,7 @@ func serializeUTXO(w io.Writer, entry *UTXOEntry, outpoint *domainmessage.Outpoi
// p2pkhUTXOEntrySerializeSize is the serialized size for a P2PKH UTXO entry.
// 8 bytes (header code) + 8 bytes (amount) + varint for script pub key length of 25 (for P2PKH) + 25 bytes for P2PKH script.
var p2pkhUTXOEntrySerializeSize = 8 + 8 + domainmessage.VarIntSerializeSize(25) + 25
var p2pkhUTXOEntrySerializeSize = 8 + 8 + wire.VarIntSerializeSize(25) + 25
// serializeUTXOEntry encodes the entry to the given io.Writer and use compression if useCompression is true.
// The compression format is described in detail above.
@@ -185,7 +180,7 @@ func serializeUTXOEntry(w io.Writer, entry *UTXOEntry) error {
return err
}
err = domainmessage.WriteVarInt(w, uint64(len(entry.ScriptPubKey())))
err = wire.WriteVarInt(w, uint64(len(entry.ScriptPubKey())))
if err != nil {
return err
}
@@ -225,7 +220,7 @@ func deserializeUTXOEntry(r io.Reader) (*UTXOEntry, error) {
return nil, err
}
scriptPubKeyLen, err := domainmessage.ReadVarInt(r)
scriptPubKeyLen, err := wire.ReadVarInt(r)
if err != nil {
return nil, err
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/pkg/errors"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/wire"
)
const (
@@ -78,7 +78,7 @@ const (
)
// NewUTXOEntry creates a new utxoEntry representing the given txOut
func NewUTXOEntry(txOut *domainmessage.TxOut, isCoinbase bool, blockBlueScore uint64) *UTXOEntry {
func NewUTXOEntry(txOut *wire.TxOut, isCoinbase bool, blockBlueScore uint64) *UTXOEntry {
entry := &UTXOEntry{
amount: txOut.Value,
scriptPubKey: txOut.ScriptPubKey,
@@ -93,7 +93,7 @@ func NewUTXOEntry(txOut *domainmessage.TxOut, isCoinbase bool, blockBlueScore ui
}
// utxoCollection represents a set of UTXOs indexed by their outpoints
type utxoCollection map[domainmessage.Outpoint]*UTXOEntry
type utxoCollection map[wire.Outpoint]*UTXOEntry
func (uc utxoCollection) String() string {
utxoStrings := make([]string, len(uc))
@@ -112,31 +112,31 @@ func (uc utxoCollection) String() string {
}
// add adds a new UTXO entry to this collection
func (uc utxoCollection) add(outpoint domainmessage.Outpoint, entry *UTXOEntry) {
func (uc utxoCollection) add(outpoint wire.Outpoint, entry *UTXOEntry) {
uc[outpoint] = entry
}
// remove removes a UTXO entry from this collection if it exists
func (uc utxoCollection) remove(outpoint domainmessage.Outpoint) {
func (uc utxoCollection) remove(outpoint wire.Outpoint) {
delete(uc, outpoint)
}
// get returns the UTXOEntry represented by provided outpoint,
// and a boolean value indicating if said UTXOEntry is in the set or not
func (uc utxoCollection) get(outpoint domainmessage.Outpoint) (*UTXOEntry, bool) {
func (uc utxoCollection) get(outpoint wire.Outpoint) (*UTXOEntry, bool) {
entry, ok := uc[outpoint]
return entry, ok
}
// contains returns a boolean value indicating whether a UTXO entry is in the set
func (uc utxoCollection) contains(outpoint domainmessage.Outpoint) bool {
func (uc utxoCollection) contains(outpoint wire.Outpoint) bool {
_, ok := uc[outpoint]
return ok
}
// containsWithBlueScore returns a boolean value indicating whether a UTXOEntry
// is in the set and its blue score is equal to the given blue score.
func (uc utxoCollection) containsWithBlueScore(outpoint domainmessage.Outpoint, blueScore uint64) bool {
func (uc utxoCollection) containsWithBlueScore(outpoint wire.Outpoint, blueScore uint64) bool {
entry, ok := uc.get(outpoint)
return ok && entry.blockBlueScore == blueScore
}
@@ -353,7 +353,7 @@ func (d *UTXODiff) clone() *UTXODiff {
//
// If d.useMultiset is true, this function MUST be
// called with the DAG lock held.
func (d *UTXODiff) AddEntry(outpoint domainmessage.Outpoint, entry *UTXOEntry) error {
func (d *UTXODiff) AddEntry(outpoint wire.Outpoint, entry *UTXOEntry) error {
if d.toRemove.containsWithBlueScore(outpoint, entry.blockBlueScore) {
d.toRemove.remove(outpoint)
} else if _, exists := d.toAdd[outpoint]; exists {
@@ -368,7 +368,7 @@ func (d *UTXODiff) AddEntry(outpoint domainmessage.Outpoint, entry *UTXOEntry) e
//
// If d.useMultiset is true, this function MUST be
// called with the DAG lock held.
func (d *UTXODiff) RemoveEntry(outpoint domainmessage.Outpoint, entry *UTXOEntry) error {
func (d *UTXODiff) RemoveEntry(outpoint wire.Outpoint, entry *UTXOEntry) error {
if d.toAdd.containsWithBlueScore(outpoint, entry.blockBlueScore) {
d.toAdd.remove(outpoint)
} else if _, exists := d.toRemove[outpoint]; exists {
@@ -399,9 +399,9 @@ type UTXOSet interface {
fmt.Stringer
diffFrom(other UTXOSet) (*UTXODiff, error)
WithDiff(utxoDiff *UTXODiff) (UTXOSet, error)
AddTx(tx *domainmessage.MsgTx, blockBlueScore uint64) (ok bool, err error)
AddTx(tx *wire.MsgTx, blockBlueScore uint64) (ok bool, err error)
clone() UTXOSet
Get(outpoint domainmessage.Outpoint) (*UTXOEntry, bool)
Get(outpoint wire.Outpoint) (*UTXOEntry, bool)
}
// FullUTXOSet represents a full list of transaction outputs and their values
@@ -456,18 +456,20 @@ func (fus *FullUTXOSet) WithDiff(other *UTXODiff) (UTXOSet, error) {
// necessarily means there's an error).
//
// This function MUST be called with the DAG lock held.
func (fus *FullUTXOSet) AddTx(tx *domainmessage.MsgTx, blueScore uint64) (isAccepted bool, err error) {
if !fus.containsInputs(tx) {
return false, nil
}
for _, txIn := range tx.TxIn {
fus.remove(txIn.PreviousOutpoint)
}
func (fus *FullUTXOSet) AddTx(tx *wire.MsgTx, blueScore uint64) (isAccepted bool, err error) {
isCoinbase := tx.IsCoinBase()
if !isCoinbase {
if !fus.containsInputs(tx) {
return false, nil
}
for _, txIn := range tx.TxIn {
fus.remove(txIn.PreviousOutpoint)
}
}
for i, txOut := range tx.TxOut {
outpoint := *domainmessage.NewOutpoint(tx.TxID(), uint32(i))
outpoint := *wire.NewOutpoint(tx.TxID(), uint32(i))
entry := NewUTXOEntry(txOut, isCoinbase, blueScore)
fus.add(outpoint, entry)
}
@@ -475,9 +477,9 @@ func (fus *FullUTXOSet) AddTx(tx *domainmessage.MsgTx, blueScore uint64) (isAcce
return true, nil
}
func (fus *FullUTXOSet) containsInputs(tx *domainmessage.MsgTx) bool {
func (fus *FullUTXOSet) containsInputs(tx *wire.MsgTx) bool {
for _, txIn := range tx.TxIn {
outpoint := *domainmessage.NewOutpoint(&txIn.PreviousOutpoint.TxID, txIn.PreviousOutpoint.Index)
outpoint := *wire.NewOutpoint(&txIn.PreviousOutpoint.TxID, txIn.PreviousOutpoint.Index)
if !fus.contains(outpoint) {
return false
}
@@ -492,7 +494,7 @@ func (fus *FullUTXOSet) clone() UTXOSet {
}
// Get returns the UTXOEntry associated with the given Outpoint, and a boolean indicating if such entry was found
func (fus *FullUTXOSet) Get(outpoint domainmessage.Outpoint) (*UTXOEntry, bool) {
func (fus *FullUTXOSet) Get(outpoint wire.Outpoint) (*UTXOEntry, bool) {
utxoEntry, ok := fus.utxoCollection[outpoint]
return utxoEntry, ok
}
@@ -540,12 +542,13 @@ func (dus *DiffUTXOSet) WithDiff(other *UTXODiff) (UTXOSet, error) {
//
// If dus.UTXODiff.useMultiset is true, this function MUST be
// called with the DAG lock held.
func (dus *DiffUTXOSet) AddTx(tx *domainmessage.MsgTx, blockBlueScore uint64) (bool, error) {
if !dus.containsInputs(tx) {
func (dus *DiffUTXOSet) AddTx(tx *wire.MsgTx, blockBlueScore uint64) (bool, error) {
isCoinbase := tx.IsCoinBase()
if !isCoinbase && !dus.containsInputs(tx) {
return false, nil
}
err := dus.appendTx(tx, blockBlueScore)
err := dus.appendTx(tx, blockBlueScore, isCoinbase)
if err != nil {
return false, err
}
@@ -553,21 +556,22 @@ func (dus *DiffUTXOSet) AddTx(tx *domainmessage.MsgTx, blockBlueScore uint64) (b
return true, nil
}
func (dus *DiffUTXOSet) appendTx(tx *domainmessage.MsgTx, blockBlueScore uint64) error {
for _, txIn := range tx.TxIn {
entry, ok := dus.Get(txIn.PreviousOutpoint)
if !ok {
return errors.Errorf("couldn't find entry for outpoint %s", txIn.PreviousOutpoint)
}
err := dus.UTXODiff.RemoveEntry(txIn.PreviousOutpoint, entry)
if err != nil {
return err
func (dus *DiffUTXOSet) appendTx(tx *wire.MsgTx, blockBlueScore uint64, isCoinbase bool) error {
if !isCoinbase {
for _, txIn := range tx.TxIn {
entry, ok := dus.Get(txIn.PreviousOutpoint)
if !ok {
return errors.Errorf("Couldn't find entry for outpoint %s", txIn.PreviousOutpoint)
}
err := dus.UTXODiff.RemoveEntry(txIn.PreviousOutpoint, entry)
if err != nil {
return err
}
}
}
isCoinbase := tx.IsCoinBase()
for i, txOut := range tx.TxOut {
outpoint := *domainmessage.NewOutpoint(tx.TxID(), uint32(i))
outpoint := *wire.NewOutpoint(tx.TxID(), uint32(i))
entry := NewUTXOEntry(txOut, isCoinbase, blockBlueScore)
err := dus.UTXODiff.AddEntry(outpoint, entry)
@@ -578,9 +582,9 @@ func (dus *DiffUTXOSet) appendTx(tx *domainmessage.MsgTx, blockBlueScore uint64)
return nil
}
func (dus *DiffUTXOSet) containsInputs(tx *domainmessage.MsgTx) bool {
func (dus *DiffUTXOSet) containsInputs(tx *wire.MsgTx) bool {
for _, txIn := range tx.TxIn {
outpoint := *domainmessage.NewOutpoint(&txIn.PreviousOutpoint.TxID, txIn.PreviousOutpoint.Index)
outpoint := *wire.NewOutpoint(&txIn.PreviousOutpoint.TxID, txIn.PreviousOutpoint.Index)
isInBase := dus.base.contains(outpoint)
isInDiffToAdd := dus.UTXODiff.toAdd.contains(outpoint)
isInDiffToRemove := dus.UTXODiff.toRemove.contains(outpoint)
@@ -626,7 +630,7 @@ func (dus *DiffUTXOSet) cloneWithoutBase() UTXOSet {
// Get returns the UTXOEntry associated with provided outpoint in this UTXOSet.
// Returns false in second output if this UTXOEntry was not found
func (dus *DiffUTXOSet) Get(outpoint domainmessage.Outpoint) (*UTXOEntry, bool) {
func (dus *DiffUTXOSet) Get(outpoint wire.Outpoint) (*UTXOEntry, bool) {
if toRemoveEntry, ok := dus.UTXODiff.toRemove.get(outpoint); ok {
// An exception is made for entries with unequal blue scores
// These are just "updates" to accepted blue score

View File

@@ -1,23 +1,24 @@
package blockdag
import (
"math"
"reflect"
"testing"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
)
// TestUTXOCollection makes sure that utxoCollection cloning and string representations work as expected.
func TestUTXOCollection(t *testing.T) {
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
txID1, _ := daghash.NewTxIDFromStr("1111111111111111111111111111111111111111111111111111111111111111")
outpoint0 := *domainmessage.NewOutpoint(txID0, 0)
outpoint1 := *domainmessage.NewOutpoint(txID1, 0)
utxoEntry0 := NewUTXOEntry(&domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 0)
utxoEntry1 := NewUTXOEntry(&domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 20}, false, 1)
outpoint0 := *wire.NewOutpoint(txID0, 0)
outpoint1 := *wire.NewOutpoint(txID1, 0)
utxoEntry0 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 0)
utxoEntry1 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 20}, false, 1)
// For each of the following test cases, we will:
// .String() the given collection and compare it to expectedStringWithMultiset
@@ -73,10 +74,10 @@ func TestUTXOCollection(t *testing.T) {
func TestUTXODiff(t *testing.T) {
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
txID1, _ := daghash.NewTxIDFromStr("1111111111111111111111111111111111111111111111111111111111111111")
outpoint0 := *domainmessage.NewOutpoint(txID0, 0)
outpoint1 := *domainmessage.NewOutpoint(txID1, 0)
utxoEntry0 := NewUTXOEntry(&domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 0)
utxoEntry1 := NewUTXOEntry(&domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 20}, false, 1)
outpoint0 := *wire.NewOutpoint(txID0, 0)
outpoint1 := *wire.NewOutpoint(txID1, 0)
utxoEntry0 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 0)
utxoEntry1 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 20}, false, 1)
// Test utxoDiff creation
@@ -119,9 +120,9 @@ func TestUTXODiff(t *testing.T) {
// Each test case represents a cell in the two tables outlined in the documentation for utxoDiff.
func TestUTXODiffRules(t *testing.T) {
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
outpoint0 := *domainmessage.NewOutpoint(txID0, 0)
utxoEntry1 := NewUTXOEntry(&domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 10)
utxoEntry2 := NewUTXOEntry(&domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 20)
outpoint0 := *wire.NewOutpoint(txID0, 0)
utxoEntry1 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 10)
utxoEntry2 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 20)
// For each of the following test cases, we will:
// this.diffFrom(other) and compare it to expectedDiffFromResult
@@ -630,10 +631,10 @@ func (dus *DiffUTXOSet) equal(other *DiffUTXOSet) bool {
func TestFullUTXOSet(t *testing.T) {
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
txID1, _ := daghash.NewTxIDFromStr("1111111111111111111111111111111111111111111111111111111111111111")
outpoint0 := *domainmessage.NewOutpoint(txID0, 0)
outpoint1 := *domainmessage.NewOutpoint(txID1, 0)
txOut0 := &domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 10}
txOut1 := &domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 20}
outpoint0 := *wire.NewOutpoint(txID0, 0)
outpoint1 := *wire.NewOutpoint(txID1, 0)
txOut0 := &wire.TxOut{ScriptPubKey: []byte{}, Value: 10}
txOut1 := &wire.TxOut{ScriptPubKey: []byte{}, Value: 20}
utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
diff := &UTXODiff{
@@ -661,8 +662,8 @@ func TestFullUTXOSet(t *testing.T) {
}
// Test fullUTXOSet addTx
txIn0 := &domainmessage.TxIn{SignatureScript: []byte{}, PreviousOutpoint: domainmessage.Outpoint{TxID: *txID0, Index: 0}, Sequence: 0}
transaction0 := domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{txIn0}, []*domainmessage.TxOut{txOut0})
txIn0 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutpoint: wire.Outpoint{TxID: *txID0, Index: 0}, Sequence: 0}
transaction0 := wire.NewNativeMsgTx(1, []*wire.TxIn{txIn0}, []*wire.TxOut{txOut0})
if isAccepted, err := emptySet.AddTx(transaction0, 0); err != nil {
t.Errorf("AddTx unexpectedly failed: %s", err)
} else if isAccepted {
@@ -694,10 +695,10 @@ func TestFullUTXOSet(t *testing.T) {
func TestDiffUTXOSet(t *testing.T) {
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
txID1, _ := daghash.NewTxIDFromStr("1111111111111111111111111111111111111111111111111111111111111111")
outpoint0 := *domainmessage.NewOutpoint(txID0, 0)
outpoint1 := *domainmessage.NewOutpoint(txID1, 0)
txOut0 := &domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 10}
txOut1 := &domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 20}
outpoint0 := *wire.NewOutpoint(txID0, 0)
outpoint1 := *wire.NewOutpoint(txID1, 0)
txOut0 := &wire.TxOut{ScriptPubKey: []byte{}, Value: 10}
txOut1 := &wire.TxOut{ScriptPubKey: []byte{}, Value: 20}
utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
diff := &UTXODiff{
@@ -946,29 +947,32 @@ func TestUTXOSetDiffRules(t *testing.T) {
// TestDiffUTXOSet_addTx makes sure that diffUTXOSet addTx works as expected
func TestDiffUTXOSet_addTx(t *testing.T) {
txOut0 := &domainmessage.TxOut{ScriptPubKey: []byte{0}, Value: 10}
// coinbaseTX is coinbase. As such, it has exactly one input with hash zero and MaxUInt32 index
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
txIn0 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutpoint: wire.Outpoint{TxID: *txID0, Index: math.MaxUint32}, Sequence: 0}
txOut0 := &wire.TxOut{ScriptPubKey: []byte{0}, Value: 10}
utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
coinbaseTX := domainmessage.NewSubnetworkMsgTx(1, []*domainmessage.TxIn{}, []*domainmessage.TxOut{txOut0}, subnetworkid.SubnetworkIDCoinbase, 0, nil)
coinbaseTX := wire.NewSubnetworkMsgTx(1, []*wire.TxIn{txIn0}, []*wire.TxOut{txOut0}, subnetworkid.SubnetworkIDCoinbase, 0, nil)
// transaction1 spends coinbaseTX
id1 := coinbaseTX.TxID()
outpoint1 := *domainmessage.NewOutpoint(id1, 0)
txIn1 := &domainmessage.TxIn{SignatureScript: []byte{}, PreviousOutpoint: outpoint1, Sequence: 0}
txOut1 := &domainmessage.TxOut{ScriptPubKey: []byte{1}, Value: 20}
outpoint1 := *wire.NewOutpoint(id1, 0)
txIn1 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutpoint: outpoint1, Sequence: 0}
txOut1 := &wire.TxOut{ScriptPubKey: []byte{1}, Value: 20}
utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
transaction1 := domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{txIn1}, []*domainmessage.TxOut{txOut1})
transaction1 := wire.NewNativeMsgTx(1, []*wire.TxIn{txIn1}, []*wire.TxOut{txOut1})
// transaction2 spends transaction1
id2 := transaction1.TxID()
outpoint2 := *domainmessage.NewOutpoint(id2, 0)
txIn2 := &domainmessage.TxIn{SignatureScript: []byte{}, PreviousOutpoint: outpoint2, Sequence: 0}
txOut2 := &domainmessage.TxOut{ScriptPubKey: []byte{2}, Value: 30}
outpoint2 := *wire.NewOutpoint(id2, 0)
txIn2 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutpoint: outpoint2, Sequence: 0}
txOut2 := &wire.TxOut{ScriptPubKey: []byte{2}, Value: 30}
utxoEntry2 := NewUTXOEntry(txOut2, false, 2)
transaction2 := domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{txIn2}, []*domainmessage.TxOut{txOut2})
transaction2 := wire.NewNativeMsgTx(1, []*wire.TxIn{txIn2}, []*wire.TxOut{txOut2})
// outpoint3 is the outpoint for transaction2
id3 := transaction2.TxID()
outpoint3 := *domainmessage.NewOutpoint(id3, 0)
outpoint3 := *wire.NewOutpoint(id3, 0)
// For each of the following test cases, we will:
// 1. startSet.addTx() all the transactions in toAdd, in order, with the initial block height startHeight
@@ -977,14 +981,14 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
name string
startSet *DiffUTXOSet
startHeight uint64
toAdd []*domainmessage.MsgTx
toAdd []*wire.MsgTx
expectedSet *DiffUTXOSet
}{
{
name: "add coinbase transaction to empty set",
startSet: NewDiffUTXOSet(NewFullUTXOSet(), NewUTXODiff()),
startHeight: 0,
toAdd: []*domainmessage.MsgTx{coinbaseTX},
toAdd: []*wire.MsgTx{coinbaseTX},
expectedSet: &DiffUTXOSet{
base: &FullUTXOSet{utxoCollection: utxoCollection{}},
UTXODiff: &UTXODiff{
@@ -997,7 +1001,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
name: "add regular transaction to empty set",
startSet: NewDiffUTXOSet(NewFullUTXOSet(), NewUTXODiff()),
startHeight: 0,
toAdd: []*domainmessage.MsgTx{transaction1},
toAdd: []*wire.MsgTx{transaction1},
expectedSet: &DiffUTXOSet{
base: &FullUTXOSet{utxoCollection: utxoCollection{}},
UTXODiff: &UTXODiff{
@@ -1016,7 +1020,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
},
},
startHeight: 1,
toAdd: []*domainmessage.MsgTx{transaction1},
toAdd: []*wire.MsgTx{transaction1},
expectedSet: &DiffUTXOSet{
base: &FullUTXOSet{utxoCollection: utxoCollection{outpoint1: utxoEntry0}},
UTXODiff: &UTXODiff{
@@ -1035,7 +1039,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
},
},
startHeight: 1,
toAdd: []*domainmessage.MsgTx{transaction1},
toAdd: []*wire.MsgTx{transaction1},
expectedSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &UTXODiff{
@@ -1054,7 +1058,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
},
},
startHeight: 1,
toAdd: []*domainmessage.MsgTx{transaction1},
toAdd: []*wire.MsgTx{transaction1},
expectedSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &UTXODiff{
@@ -1073,7 +1077,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
},
},
startHeight: 1,
toAdd: []*domainmessage.MsgTx{transaction1, transaction2},
toAdd: []*wire.MsgTx{transaction1, transaction2},
expectedSet: &DiffUTXOSet{
base: &FullUTXOSet{utxoCollection: utxoCollection{outpoint1: utxoEntry0}},
UTXODiff: &UTXODiff{
@@ -1125,16 +1129,16 @@ func (dus *DiffUTXOSet) collection() (utxoCollection, error) {
func TestUTXOSetAddEntry(t *testing.T) {
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
txID1, _ := daghash.NewTxIDFromStr("1111111111111111111111111111111111111111111111111111111111111111")
outpoint0 := domainmessage.NewOutpoint(txID0, 0)
outpoint1 := domainmessage.NewOutpoint(txID1, 0)
utxoEntry0 := NewUTXOEntry(&domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 0)
utxoEntry1 := NewUTXOEntry(&domainmessage.TxOut{ScriptPubKey: []byte{}, Value: 20}, false, 1)
outpoint0 := wire.NewOutpoint(txID0, 0)
outpoint1 := wire.NewOutpoint(txID1, 0)
utxoEntry0 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 0)
utxoEntry1 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 20}, false, 1)
utxoDiff := NewUTXODiff()
tests := []struct {
name string
outpointToAdd *domainmessage.Outpoint
outpointToAdd *wire.Outpoint
utxoEntryToAdd *UTXOEntry
expectedUTXODiff *UTXODiff
expectedError string

View File

@@ -6,20 +6,18 @@ package blockdag
import (
"fmt"
"github.com/kaspanet/go-secp256k1"
"math"
"sort"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/util/mstime"
"time"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
)
const (
@@ -47,7 +45,7 @@ const (
// isNullOutpoint determines whether or not a previous transaction outpoint
// is set.
func isNullOutpoint(outpoint *domainmessage.Outpoint) bool {
func isNullOutpoint(outpoint *wire.Outpoint) bool {
if outpoint.Index == math.MaxUint32 && outpoint.TxID == daghash.ZeroTxID {
return true
}
@@ -58,12 +56,12 @@ func isNullOutpoint(outpoint *domainmessage.Outpoint) bool {
// met, meaning that all the inputs of a given transaction have reached a
// blue score or time sufficient for their relative lock-time maturity.
func SequenceLockActive(sequenceLock *SequenceLock, blockBlueScore uint64,
medianTimePast mstime.Time) bool {
medianTimePast time.Time) bool {
// If either the milliseconds, or blue score relative-lock time has not yet
// If either the seconds, or blue score relative-lock time has not yet
// reached, then the transaction is not yet mature according to its
// sequence locks.
if sequenceLock.Milliseconds >= medianTimePast.UnixMilliseconds() ||
if sequenceLock.Seconds >= medianTimePast.Unix() ||
sequenceLock.BlockBlueScore >= int64(blockBlueScore) {
return false
}
@@ -72,7 +70,7 @@ func SequenceLockActive(sequenceLock *SequenceLock, blockBlueScore uint64,
}
// IsFinalizedTransaction determines whether or not a transaction is finalized.
func IsFinalizedTransaction(tx *util.Tx, blockBlueScore uint64, blockTime mstime.Time) bool {
func IsFinalizedTransaction(tx *util.Tx, blockBlueScore uint64, blockTime time.Time) bool {
msgTx := tx.MsgTx()
// Lock time of zero means the transaction is finalized.
@@ -89,7 +87,7 @@ func IsFinalizedTransaction(tx *util.Tx, blockBlueScore uint64, blockTime mstime
if lockTime < txscript.LockTimeThreshold {
blockTimeOrBlueScore = int64(blockBlueScore)
} else {
blockTimeOrBlueScore = blockTime.UnixMilliseconds()
blockTimeOrBlueScore = blockTime.Unix()
}
if int64(lockTime) < blockTimeOrBlueScore {
return true
@@ -128,55 +126,22 @@ func CalcBlockSubsidy(blueScore uint64, dagParams *dagconfig.Params) uint64 {
// CheckTransactionSanity performs some preliminary checks on a transaction to
// ensure it is sane. These checks are context free.
func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID) error {
err := checkTransactionInputCount(tx)
if err != nil {
return err
}
err = checkTransactionAmountRanges(tx)
if err != nil {
return err
}
err = checkDuplicateTransactionInputs(tx)
if err != nil {
return err
}
err = checkCoinbaseLength(tx)
if err != nil {
return err
}
err = checkTransactionPayloadHash(tx)
if err != nil {
return err
}
err = checkGasInBuiltInOrNativeTransactions(tx)
if err != nil {
return err
}
err = checkSubnetworkRegistryTransaction(tx)
if err != nil {
return err
}
err = checkNativeTransactionPayload(tx)
if err != nil {
return err
}
err = checkTransactionSubnetwork(tx, subnetworkID)
if err != nil {
return err
}
return nil
}
func checkTransactionInputCount(tx *util.Tx) error {
// A non-coinbase transaction must have at least one input.
isCoinbase := tx.IsCoinBase()
// A transaction must have at least one input.
msgTx := tx.MsgTx()
if !tx.IsCoinBase() && len(msgTx.TxIn) == 0 {
if !isCoinbase && len(msgTx.TxIn) == 0 {
return ruleError(ErrNoTxInputs, "transaction has no inputs")
}
return nil
}
func checkTransactionAmountRanges(tx *util.Tx) error {
// A transaction must not exceed the maximum allowed block mass when
// serialized.
serializedTxSize := msgTx.SerializeSize()
if serializedTxSize*MassPerTxByte > wire.MaxMassPerTx {
str := fmt.Sprintf("serialized transaction is too big - got "+
"%d, max %d", serializedTxSize, wire.MaxMassPerBlock)
return ruleError(ErrTxMassTooHigh, str)
}
// Ensure the transaction amounts are in range. Each transaction
// output must not be negative or more than the max allowed per
// transaction. Also, the total of all outputs must abide by the same
@@ -184,7 +149,7 @@ func checkTransactionAmountRanges(tx *util.Tx) error {
// as a sompi. One kaspa is a quantity of sompi as defined by the
// SompiPerKaspa constant.
var totalSompi uint64
for _, txOut := range tx.MsgTx().TxOut {
for _, txOut := range msgTx.TxOut {
sompi := txOut.Value
if sompi > util.MaxSompi {
str := fmt.Sprintf("transaction output value of %d is "+
@@ -212,25 +177,20 @@ func checkTransactionAmountRanges(tx *util.Tx) error {
return ruleError(ErrBadTxOutValue, str)
}
}
return nil
}
func checkDuplicateTransactionInputs(tx *util.Tx) error {
existingTxOut := make(map[domainmessage.Outpoint]struct{})
for _, txIn := range tx.MsgTx().TxIn {
// Check for duplicate transaction inputs.
existingTxOut := make(map[wire.Outpoint]struct{})
for _, txIn := range msgTx.TxIn {
if _, exists := existingTxOut[txIn.PreviousOutpoint]; exists {
return ruleError(ErrDuplicateTxInputs, "transaction "+
"contains duplicate inputs")
}
existingTxOut[txIn.PreviousOutpoint] = struct{}{}
}
return nil
}
func checkCoinbaseLength(tx *util.Tx) error {
// Coinbase payload length must not exceed the max length.
if tx.IsCoinBase() {
payloadLen := len(tx.MsgTx().Payload)
if isCoinbase {
payloadLen := len(msgTx.Payload)
if payloadLen > MaxCoinbasePayloadLen {
str := fmt.Sprintf("coinbase transaction payload length "+
"of %d is out of range (max: %d)",
@@ -240,7 +200,7 @@ func checkCoinbaseLength(tx *util.Tx) error {
} else {
// Previous transaction outputs referenced by the inputs to this
// transaction must not be null.
for _, txIn := range tx.MsgTx().TxIn {
for _, txIn := range msgTx.TxIn {
if isNullOutpoint(&txIn.PreviousOutpoint) {
return ruleError(ErrBadTxInput, "transaction "+
"input refers to previous output that "+
@@ -248,11 +208,8 @@ func checkCoinbaseLength(tx *util.Tx) error {
}
}
}
return nil
}
func checkTransactionPayloadHash(tx *util.Tx) error {
msgTx := tx.MsgTx()
// Check payload's hash
if !msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
payloadHash := daghash.DoubleHashH(msgTx.Payload)
if !msgTx.PayloadHash.IsEqual(&payloadHash) {
@@ -261,41 +218,32 @@ func checkTransactionPayloadHash(tx *util.Tx) error {
} else if msgTx.PayloadHash != nil {
return ruleError(ErrInvalidPayloadHash, "unexpected non-empty payload hash in native subnetwork")
}
return nil
}
func checkGasInBuiltInOrNativeTransactions(tx *util.Tx) error {
// Transactions in native, registry and coinbase subnetworks must have Gas = 0
msgTx := tx.MsgTx()
if msgTx.SubnetworkID.IsBuiltInOrNative() && msgTx.Gas > 0 {
if (msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) ||
msgTx.SubnetworkID.IsBuiltIn()) &&
msgTx.Gas > 0 {
return ruleError(ErrInvalidGas, "transaction in the native or "+
"registry subnetworks has gas > 0 ")
}
return nil
}
func checkSubnetworkRegistryTransaction(tx *util.Tx) error {
if tx.MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDRegistry) {
err := validateSubnetworkRegistryTransaction(tx.MsgTx())
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDRegistry) {
err := validateSubnetworkRegistryTransaction(msgTx)
if err != nil {
return err
}
}
return nil
}
func checkNativeTransactionPayload(tx *util.Tx) error {
msgTx := tx.MsgTx()
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) && len(msgTx.Payload) > 0 {
return ruleError(ErrInvalidPayload, "transaction in the native subnetwork includes a payload")
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) &&
len(msgTx.Payload) > 0 {
return ruleError(ErrInvalidPayload,
"transaction in the native subnetwork includes a payload")
}
return nil
}
func checkTransactionSubnetwork(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID) error {
// If we are a partial node, only transactions on built in subnetworks
// or our own subnetwork may have a payload
msgTx := tx.MsgTx()
isLocalNodeFull := subnetworkID == nil
shouldTxBeFull := msgTx.SubnetworkID.IsBuiltIn() ||
msgTx.SubnetworkID.IsEqual(subnetworkID)
@@ -304,6 +252,7 @@ func checkTransactionSubnetwork(tx *util.Tx, subnetworkID *subnetworkid.Subnetwo
"transaction that was expected to be partial has a payload "+
"with length > 0")
}
return nil
}
@@ -314,7 +263,7 @@ func checkTransactionSubnetwork(tx *util.Tx, subnetworkID *subnetworkid.Subnetwo
// The flags modify the behavior of this function as follows:
// - BFNoPoWCheck: The check to ensure the block hash is less than the target
// difficulty is not performed.
func (dag *BlockDAG) checkProofOfWork(header *domainmessage.BlockHeader, flags BehaviorFlags) error {
func (dag *BlockDAG) checkProofOfWork(header *wire.BlockHeader, flags BehaviorFlags) error {
// The target difficulty must be larger than zero.
target := util.CompactToBig(header.Bits)
if target.Sign() <= 0 {
@@ -324,9 +273,9 @@ func (dag *BlockDAG) checkProofOfWork(header *domainmessage.BlockHeader, flags B
}
// The target difficulty must be less than the maximum allowed.
if target.Cmp(dag.Params.PowMax) > 0 {
if target.Cmp(dag.dagParams.PowMax) > 0 {
str := fmt.Sprintf("block target difficulty of %064x is "+
"higher than max of %064x", target, dag.Params.PowMax)
"higher than max of %064x", target, dag.dagParams.PowMax)
return ruleError(ErrUnexpectedDifficulty, str)
}
@@ -354,9 +303,9 @@ func ValidateTxMass(tx *util.Tx, utxoSet UTXOSet) error {
if err != nil {
return err
}
if txMass > domainmessage.MaxMassPerBlock {
if txMass > wire.MaxMassPerBlock {
str := fmt.Sprintf("tx %s has mass %d, which is above the "+
"allowed limit of %d", tx.ID(), txMass, domainmessage.MaxMassPerBlock)
"allowed limit of %d", tx.ID(), txMass, wire.MaxMassPerBlock)
return ruleError(ErrTxMassTooHigh, str)
}
return nil
@@ -380,9 +329,9 @@ func CalcBlockMass(pastUTXO UTXOSet, transactions []*util.Tx) (uint64, error) {
// We could potentially overflow the accumulator so check for
// overflow as well.
if totalMass < txMass || totalMass > domainmessage.MaxMassPerBlock {
if totalMass < txMass || totalMass > wire.MaxMassPerBlock {
str := fmt.Sprintf("block has total mass %d, which is "+
"above the allowed limit of %d", totalMass, domainmessage.MaxMassPerBlock)
"above the allowed limit of %d", totalMass, wire.MaxMassPerBlock)
return 0, ruleError(ErrBlockMassTooHigh, str)
}
}
@@ -451,32 +400,52 @@ func CalcTxMass(tx *util.Tx, previousScriptPubKeys [][]byte) uint64 {
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkProofOfWork.
func (dag *BlockDAG) checkBlockHeaderSanity(block *util.Block, flags BehaviorFlags) error {
func (dag *BlockDAG) checkBlockHeaderSanity(header *wire.BlockHeader, flags BehaviorFlags) (delay time.Duration, err error) {
// Ensure the proof of work bits in the block header is in min/max range
// and the block hash is less than the target value described by the
// bits.
header := &block.MsgBlock().Header
err := dag.checkProofOfWork(header, flags)
err = dag.checkProofOfWork(header, flags)
if err != nil {
return err
return 0, err
}
if len(header.ParentHashes) == 0 {
if !header.BlockHash().IsEqual(dag.Params.GenesisHash) {
return ruleError(ErrNoParents, "block has no parents")
if !header.BlockHash().IsEqual(dag.dagParams.GenesisHash) {
return 0, ruleError(ErrNoParents, "block has no parents")
}
} else {
err = checkBlockParentsOrder(header)
if err != nil {
return err
return 0, err
}
}
return nil
// A block timestamp must not have a greater precision than one second.
// This check is necessary because Go time.Time values support
// nanosecond precision whereas the consensus rules only apply to
// seconds and it's much nicer to deal with standard Go time values
// instead of converting to seconds everywhere.
if !header.Timestamp.Equal(time.Unix(header.Timestamp.Unix(), 0)) {
str := fmt.Sprintf("block timestamp of %s has a higher "+
"precision than one second", header.Timestamp)
return 0, ruleError(ErrInvalidTime, str)
}
// Ensure the block time is not too far in the future. If it's too far, return
// the duration of time that should be waited before the block becomes valid.
// This check needs to be last as it does not return an error but rather marks the
// header as delayed (and valid).
maxTimestamp := dag.Now().Add(time.Second *
time.Duration(int64(dag.TimestampDeviationTolerance)*dag.targetTimePerBlock))
if header.Timestamp.After(maxTimestamp) {
return header.Timestamp.Sub(maxTimestamp), nil
}
return 0, nil
}
//checkBlockParentsOrder ensures that the block's parents are ordered by hash
func checkBlockParentsOrder(header *domainmessage.BlockHeader) error {
func checkBlockParentsOrder(header *wire.BlockHeader) error {
sortedHashes := make([]*daghash.Hash, header.NumParentBlocks())
for i, hash := range header.ParentHashes {
sortedHashes[i] = hash
@@ -495,183 +464,102 @@ func checkBlockParentsOrder(header *domainmessage.BlockHeader) error {
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkBlockHeaderSanity.
func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) error {
err := dag.checkBlockHeaderSanity(block, flags)
func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) (time.Duration, error) {
msgBlock := block.MsgBlock()
header := &msgBlock.Header
delay, err := dag.checkBlockHeaderSanity(header, flags)
if err != nil {
return err
}
err = dag.checkBlockContainsAtLeastOneTransaction(block)
if err != nil {
return err
}
err = dag.checkBlockContainsLessThanMaxBlockMassTransactions(block)
if err != nil {
return err
}
err = dag.checkFirstBlockTransactionIsCoinbase(block)
if err != nil {
return err
}
err = dag.checkBlockContainsOnlyOneCoinbase(block)
if err != nil {
return err
}
err = dag.checkBlockTransactionOrder(block)
if err != nil {
return err
}
err = dag.checkNoNonNativeTransactions(block)
if err != nil {
return err
}
err = dag.checkBlockTransactionSanity(block)
if err != nil {
return err
}
err = dag.checkBlockHashMerkleRoot(block)
if err != nil {
return err
return 0, err
}
// The following check will be fairly quick since the transaction IDs
// are already cached due to building the merkle tree above.
err = dag.checkBlockDuplicateTransactions(block)
if err != nil {
return err
}
err = dag.checkBlockDoubleSpends(block)
if err != nil {
return err
}
return nil
}
func (dag *BlockDAG) checkBlockContainsAtLeastOneTransaction(block *util.Block) error {
transactions := block.Transactions()
numTx := len(transactions)
// A block must have at least one transaction.
numTx := len(msgBlock.Transactions)
if numTx == 0 {
return ruleError(ErrNoTransactions, "block does not contain "+
return 0, ruleError(ErrNoTransactions, "block does not contain "+
"any transactions")
}
return nil
}
func (dag *BlockDAG) checkBlockContainsLessThanMaxBlockMassTransactions(block *util.Block) error {
// A block must not have more transactions than the max block mass or
// else it is certainly over the block mass limit.
transactions := block.Transactions()
numTx := len(transactions)
if numTx > domainmessage.MaxMassPerBlock {
if numTx > wire.MaxMassPerBlock {
str := fmt.Sprintf("block contains too many transactions - "+
"got %d, max %d", numTx, domainmessage.MaxMassPerBlock)
return ruleError(ErrBlockMassTooHigh, str)
"got %d, max %d", numTx, wire.MaxMassPerBlock)
return 0, ruleError(ErrBlockMassTooHigh, str)
}
return nil
}
func (dag *BlockDAG) checkFirstBlockTransactionIsCoinbase(block *util.Block) error {
// The first transaction in a block must be a coinbase.
transactions := block.Transactions()
if !transactions[util.CoinbaseTransactionIndex].IsCoinBase() {
return ruleError(ErrFirstTxNotCoinbase, "first transaction in "+
return 0, ruleError(ErrFirstTxNotCoinbase, "first transaction in "+
"block is not a coinbase")
}
return nil
}
func (dag *BlockDAG) checkBlockContainsOnlyOneCoinbase(block *util.Block) error {
transactions := block.Transactions()
for i, tx := range transactions[util.CoinbaseTransactionIndex+1:] {
txOffset := util.CoinbaseTransactionIndex + 1
// A block must not have more than one coinbase. And transactions must be
// ordered by subnetwork
for i, tx := range transactions[txOffset:] {
if tx.IsCoinBase() {
str := fmt.Sprintf("block contains second coinbase at "+
"index %d", i+2)
return ruleError(ErrMultipleCoinbases, str)
return 0, ruleError(ErrMultipleCoinbases, str)
}
}
return nil
}
func (dag *BlockDAG) checkBlockTransactionOrder(block *util.Block) error {
transactions := block.Transactions()
for i, tx := range transactions[util.CoinbaseTransactionIndex+1:] {
if i != 0 && subnetworkid.Less(&tx.MsgTx().SubnetworkID, &transactions[i].MsgTx().SubnetworkID) {
return ruleError(ErrTransactionsNotSorted, "transactions must be sorted by subnetwork")
return 0, ruleError(ErrTransactionsNotSorted, "transactions must be sorted by subnetwork")
}
}
return nil
}
func (dag *BlockDAG) checkNoNonNativeTransactions(block *util.Block) error {
// Disallow non-native/coinbase subnetworks in networks that don't allow them
if !dag.Params.EnableNonNativeSubnetworks {
transactions := block.Transactions()
for _, tx := range transactions {
if !(tx.MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) ||
tx.MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDCoinbase)) {
return ruleError(ErrInvalidSubnetwork, "non-native/coinbase subnetworks are not allowed")
}
}
}
return nil
}
func (dag *BlockDAG) checkBlockTransactionSanity(block *util.Block) error {
transactions := block.Transactions()
// Do some preliminary checks on each transaction to ensure they are
// sane before continuing.
for _, tx := range transactions {
err := CheckTransactionSanity(tx, dag.subnetworkID)
if err != nil {
return err
return 0, err
}
}
return nil
}
func (dag *BlockDAG) checkBlockHashMerkleRoot(block *util.Block) error {
// Build merkle tree and ensure the calculated merkle root matches the
// entry in the block header. This also has the effect of caching all
// of the transaction hashes in the block to speed up future hash
// checks.
hashMerkleTree := BuildHashMerkleTreeStore(block.Transactions())
calculatedHashMerkleRoot := hashMerkleTree.Root()
if !block.MsgBlock().Header.HashMerkleRoot.IsEqual(calculatedHashMerkleRoot) {
if !header.HashMerkleRoot.IsEqual(calculatedHashMerkleRoot) {
str := fmt.Sprintf("block hash merkle root is invalid - block "+
"header indicates %s, but calculated value is %s",
block.MsgBlock().Header.HashMerkleRoot, calculatedHashMerkleRoot)
return ruleError(ErrBadMerkleRoot, str)
header.HashMerkleRoot, calculatedHashMerkleRoot)
return 0, ruleError(ErrBadMerkleRoot, str)
}
return nil
}
func (dag *BlockDAG) checkBlockDuplicateTransactions(block *util.Block) error {
// Check for duplicate transactions. This check will be fairly quick
// since the transaction IDs are already cached due to building the
// merkle tree above.
existingTxIDs := make(map[daghash.TxID]struct{})
transactions := block.Transactions()
for _, tx := range transactions {
id := tx.ID()
if _, exists := existingTxIDs[*id]; exists {
str := fmt.Sprintf("block contains duplicate "+
"transaction %s", id)
return ruleError(ErrDuplicateTx, str)
return 0, ruleError(ErrDuplicateTx, str)
}
existingTxIDs[*id] = struct{}{}
}
return nil
}
func (dag *BlockDAG) checkBlockDoubleSpends(block *util.Block) error {
usedOutpoints := make(map[domainmessage.Outpoint]*daghash.TxID)
transactions := block.Transactions()
// Check for double spends with transactions on the same block.
usedOutpoints := make(map[wire.Outpoint]*daghash.TxID)
for _, tx := range transactions {
for _, txIn := range tx.MsgTx().TxIn {
if spendingTxID, exists := usedOutpoints[txIn.PreviousOutpoint]; exists {
str := fmt.Sprintf("transaction %s spends "+
"outpoint %s that was already spent by "+
"transaction %s in this block", tx.ID(), txIn.PreviousOutpoint, spendingTxID)
return ruleError(ErrDoubleSpendInSameBlock, str)
return 0, ruleError(ErrDoubleSpendInSameBlock, str)
}
usedOutpoints[txIn.PreviousOutpoint] = tx.ID()
}
}
return nil
return delay, nil
}
// checkBlockHeaderContext performs several validation checks on the block header
@@ -681,7 +569,7 @@ func (dag *BlockDAG) checkBlockDoubleSpends(block *util.Block) error {
// - BFFastAdd: No checks are performed.
//
// This function MUST be called with the dag state lock held (for writes).
func (dag *BlockDAG) checkBlockHeaderContext(header *domainmessage.BlockHeader, bluestParent *blockNode, fastAdd bool) error {
func (dag *BlockDAG) checkBlockHeaderContext(header *wire.BlockHeader, bluestParent *blockNode, fastAdd bool) error {
if !fastAdd {
if err := dag.validateDifficulty(header, bluestParent); err != nil {
return err
@@ -694,7 +582,7 @@ func (dag *BlockDAG) checkBlockHeaderContext(header *domainmessage.BlockHeader,
return nil
}
func validateMedianTime(dag *BlockDAG, header *domainmessage.BlockHeader, bluestParent *blockNode) error {
func validateMedianTime(dag *BlockDAG, header *wire.BlockHeader, bluestParent *blockNode) error {
if !header.IsGenesis() {
// Ensure the timestamp for the block header is not before the
// median time of the last several blocks (medianTimeBlocks).
@@ -708,7 +596,7 @@ func validateMedianTime(dag *BlockDAG, header *domainmessage.BlockHeader, bluest
return nil
}
func (dag *BlockDAG) validateDifficulty(header *domainmessage.BlockHeader, bluestParent *blockNode) error {
func (dag *BlockDAG) validateDifficulty(header *wire.BlockHeader, bluestParent *blockNode) error {
// Ensure the difficulty specified in the block header matches
// the calculated difficulty based on the previous block and
// difficulty retarget rules.
@@ -724,11 +612,11 @@ func (dag *BlockDAG) validateDifficulty(header *domainmessage.BlockHeader, blues
}
// validateParents validates that no parent is an ancestor of another parent, and no parent is finalized
func (dag *BlockDAG) validateParents(blockHeader *domainmessage.BlockHeader, parents blockSet) error {
func (dag *BlockDAG) validateParents(blockHeader *wire.BlockHeader, parents blockSet) error {
for parentA := range parents {
// isFinalized might be false-negative because node finality status is
// updated in a separate goroutine. This is why later the block is
// checked more thoroughly on the finality rules in dag.checkFinalityViolation.
// checked more thoroughly on the finality rules in dag.checkFinalityRules.
if parentA.isFinalized {
return ruleError(ErrFinality, fmt.Sprintf("block %s is a finalized "+
"parent of block %s", parentA.hash, blockHeader.BlockHash()))
@@ -739,7 +627,7 @@ func (dag *BlockDAG) validateParents(blockHeader *domainmessage.BlockHeader, par
continue
}
isAncestorOf, err := dag.isInPast(parentA, parentB)
isAncestorOf, err := dag.isAncestorOf(parentA, parentB)
if err != nil {
return err
}
@@ -767,16 +655,11 @@ func (dag *BlockDAG) validateParents(blockHeader *domainmessage.BlockHeader, par
// for how the flags modify its behavior.
//
// This function MUST be called with the dag state lock held (for writes).
func (dag *BlockDAG) checkBlockContext(block *util.Block, flags BehaviorFlags) error {
parents, err := lookupParentNodes(block, dag)
if err != nil {
return dag.handleLookupParentNodesError(block, err)
}
func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, flags BehaviorFlags) error {
bluestParent := parents.bluest()
fastAdd := flags&BFFastAdd == BFFastAdd
err = dag.validateParents(&block.MsgBlock().Header, parents)
err := dag.validateParents(&block.MsgBlock().Header, parents)
if err != nil {
return err
}
@@ -790,26 +673,10 @@ func (dag *BlockDAG) checkBlockContext(block *util.Block, flags BehaviorFlags) e
return nil
}
func (dag *BlockDAG) handleLookupParentNodesError(block *util.Block, err error) error {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrInvalidAncestorBlock {
err := dag.addNodeToIndexWithInvalidAncestor(block)
if err != nil {
return err
}
}
return err
}
func (dag *BlockDAG) checkBlockTransactionsFinalized(block *util.Block, node *blockNode, flags BehaviorFlags) error {
fastAdd := flags&BFFastAdd == BFFastAdd || dag.index.NodeStatus(node).KnownValid()
if fastAdd {
return nil
}
func (dag *BlockDAG) validateAllTxsFinalized(block *util.Block, node *blockNode, bluestParent *blockNode) error {
blockTime := block.MsgBlock().Header.Timestamp
if !block.IsGenesis() {
blockTime = node.selectedParent.PastMedianTime(dag)
blockTime = bluestParent.PastMedianTime(dag)
}
// Ensure all transactions in the block are finalized.
@@ -836,9 +703,9 @@ func (dag *BlockDAG) checkBlockTransactionsFinalized(block *util.Block, node *bl
func ensureNoDuplicateTx(utxoSet UTXOSet, transactions []*util.Tx) error {
// Fetch utxos for all of the transaction ouputs in this block.
// Typically, there will not be any utxos for any of the outputs.
fetchSet := make(map[domainmessage.Outpoint]struct{})
fetchSet := make(map[wire.Outpoint]struct{})
for _, tx := range transactions {
prevOut := domainmessage.Outpoint{TxID: *tx.ID()}
prevOut := wire.Outpoint{TxID: *tx.ID()}
for txOutIdx := range tx.MsgTx().TxOut {
prevOut.Index = uint32(txOutIdx)
fetchSet[prevOut] = struct{}{}
@@ -945,7 +812,7 @@ func CheckTransactionInputsAndCalulateFee(tx *util.Tx, txBlueScore uint64, utxoS
return txFeeInSompi, nil
}
func validateCoinbaseMaturity(dagParams *dagconfig.Params, entry *UTXOEntry, txBlueScore uint64, txIn *domainmessage.TxIn) error {
func validateCoinbaseMaturity(dagParams *dagconfig.Params, entry *UTXOEntry, txBlueScore uint64, txIn *wire.TxIn) error {
// Ensure the transaction is not spending coins which have not
// yet reached the required coinbase maturity.
if entry.IsCoinbase() {
@@ -1009,7 +876,7 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
for _, tx := range transactions {
txFee, err := CheckTransactionInputsAndCalulateFee(tx, block.blueScore, pastUTXO,
dag.Params, fastAdd)
dag.dagParams, fastAdd)
if err != nil {
return nil, err
}
@@ -1050,7 +917,7 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
// A transaction can only be included within a block
// once the sequence locks of *all* its inputs are
// active.
sequenceLock, err := dag.calcSequenceLock(block, pastUTXO, tx)
sequenceLock, err := dag.calcSequenceLock(block, pastUTXO, tx, false)
if err != nil {
return nil, err
}
@@ -1075,18 +942,6 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
return feeData, nil
}
func (node *blockNode) validateUTXOCommitment(multiset *secp256k1.MultiSet) error {
calculatedMultisetHash := daghash.Hash(*multiset.Finalize())
if !calculatedMultisetHash.IsEqual(node.utxoCommitment) {
str := fmt.Sprintf("block %s UTXO commitment is invalid - block "+
"header indicates %s, but calculated value is %s", node.hash,
node.utxoCommitment, calculatedMultisetHash)
return ruleError(ErrBadUTXOCommitment, str)
}
return nil
}
// CheckConnectBlockTemplate fully validates that connecting the passed block to
// the DAG does not violate any consensus rules, aside from the proof of
// work requirement.
@@ -1108,17 +963,21 @@ func (dag *BlockDAG) CheckConnectBlockTemplateNoLock(block *util.Block) error {
header := block.MsgBlock().Header
err := dag.checkBlockSanity(block, flags)
delay, err := dag.checkBlockSanity(block, flags)
if err != nil {
return err
}
_, isDelayed := dag.shouldBlockBeDelayed(block)
if isDelayed {
if delay != 0 {
return errors.Errorf("Block timestamp is too far in the future")
}
err = dag.checkBlockContext(block, flags)
parents, err := lookupParentNodes(block, dag)
if err != nil {
return err
}
err = dag.checkBlockContext(block, parents, flags)
if err != nil {
return err
}
@@ -1130,24 +989,3 @@ func (dag *BlockDAG) CheckConnectBlockTemplateNoLock(block *util.Block) error {
return err
}
func (dag *BlockDAG) checkDuplicateBlock(blockHash *daghash.Hash, flags BehaviorFlags) error {
wasBlockStored := flags&BFWasStored == BFWasStored
if dag.IsInDAG(blockHash) && !wasBlockStored {
str := fmt.Sprintf("already have block %s", blockHash)
return ruleError(ErrDuplicateBlock, str)
}
// The block must not already exist as an orphan.
if _, exists := dag.orphans[*blockHash]; exists {
str := fmt.Sprintf("already have block (orphan) %s", blockHash)
return ruleError(ErrDuplicateBlock, str)
}
if dag.isKnownDelayedBlock(blockHash) {
str := fmt.Sprintf("already have block (delayed) %s", blockHash)
return ruleError(ErrDuplicateBlock, str)
}
return nil
}

View File

@@ -10,51 +10,49 @@ import (
"testing"
"time"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
)
// TestSequenceLocksActive tests the SequenceLockActive function to ensure it
// works as expected in all possible combinations/scenarios.
func TestSequenceLocksActive(t *testing.T) {
seqLock := func(blueScore int64, milliseconds int64) *SequenceLock {
seqLock := func(h int64, s int64) *SequenceLock {
return &SequenceLock{
Milliseconds: milliseconds,
BlockBlueScore: blueScore,
Seconds: s,
BlockBlueScore: h,
}
}
tests := []struct {
seqLock *SequenceLock
blockBlueScore uint64
mtp mstime.Time
mtp time.Time
want bool
}{
// Block based sequence lock with equal block blue score.
{seqLock: seqLock(1000, -1), blockBlueScore: 1001, mtp: mstime.UnixMilliseconds(9), want: true},
{seqLock: seqLock(1000, -1), blockBlueScore: 1001, mtp: time.Unix(9, 0), want: true},
// Time based sequence lock with mtp past the absolute time.
{seqLock: seqLock(-1, 30), blockBlueScore: 2, mtp: mstime.UnixMilliseconds(31), want: true},
{seqLock: seqLock(-1, 30), blockBlueScore: 2, mtp: time.Unix(31, 0), want: true},
// Block based sequence lock with current blue score below seq lock block blue score.
{seqLock: seqLock(1000, -1), blockBlueScore: 90, mtp: mstime.UnixMilliseconds(9), want: false},
{seqLock: seqLock(1000, -1), blockBlueScore: 90, mtp: time.Unix(9, 0), want: false},
// Time based sequence lock with current time before lock time.
{seqLock: seqLock(-1, 30), blockBlueScore: 2, mtp: mstime.UnixMilliseconds(29), want: false},
{seqLock: seqLock(-1, 30), blockBlueScore: 2, mtp: time.Unix(29, 0), want: false},
// Block based sequence lock at the same blue score, so shouldn't yet be active.
{seqLock: seqLock(1000, -1), blockBlueScore: 1000, mtp: mstime.UnixMilliseconds(9), want: false},
{seqLock: seqLock(1000, -1), blockBlueScore: 1000, mtp: time.Unix(9, 0), want: false},
// Time based sequence lock with current time equal to lock time, so shouldn't yet be active.
{seqLock: seqLock(-1, 30), blockBlueScore: 2, mtp: mstime.UnixMilliseconds(30), want: false},
{seqLock: seqLock(-1, 30), blockBlueScore: 2, mtp: time.Unix(30, 0), want: false},
}
t.Logf("Running %d sequence locks tests", len(tests))
@@ -127,6 +125,12 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
"block 4: %v", err)
}
blockNode3 := dag.index.LookupNode(blocks[3].Hash())
blockNode4 := dag.index.LookupNode(blocks[4].Hash())
if blockNode3.children.contains(blockNode4) {
t.Errorf("Block 4 wasn't successfully detached as a child from block3")
}
// Block 3a should connect even though it does not build on dag tips.
err = dag.CheckConnectBlockTemplateNoLock(blocks[5])
if err != nil {
@@ -166,19 +170,22 @@ func TestCheckBlockSanity(t *testing.T) {
return
}
defer teardownFunc()
dag.timeSource = newFakeTimeSource(mstime.Now())
dag.timeSource = newFakeTimeSource(time.Now())
block := util.NewBlock(&Block100000)
if len(block.Transactions()) < 3 {
t.Fatalf("Too few transactions in block, expect at least 3, got %v", len(block.Transactions()))
}
err = dag.checkBlockSanity(block, BFNone)
delay, err := dag.checkBlockSanity(block, BFNone)
if err != nil {
t.Errorf("CheckBlockSanity: %v", err)
}
if delay != 0 {
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
}
// Test with block with wrong transactions sorting order
blockWithWrongTxOrder := util.NewBlock(&BlockWithWrongTxOrder)
err = dag.checkBlockSanity(blockWithWrongTxOrder, BFNone)
delay, err = dag.checkBlockSanity(blockWithWrongTxOrder, BFNone)
if err == nil {
t.Errorf("CheckBlockSanity: transactions disorder is not detected")
}
@@ -189,9 +196,24 @@ func TestCheckBlockSanity(t *testing.T) {
t.Errorf("CheckBlockSanity: wrong error returned, expect ErrTransactionsNotSorted, got"+
" %v, err %s", ruleErr.ErrorCode, err)
}
if delay != 0 {
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
}
var invalidParentsOrderBlock = domainmessage.MsgBlock{
Header: domainmessage.BlockHeader{
// Ensure a block that has a timestamp with a precision higher than one
// second fails.
timestamp := block.MsgBlock().Header.Timestamp
block.MsgBlock().Header.Timestamp = timestamp.Add(time.Nanosecond)
delay, err = dag.checkBlockSanity(block, BFNone)
if err == nil {
t.Errorf("CheckBlockSanity: error is nil when it shouldn't be")
}
if delay != 0 {
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
}
var invalidParentsOrderBlock = wire.MsgBlock{
Header: wire.BlockHeader{
Version: 0x10000000,
ParentHashes: []*daghash.Hash{
{
@@ -225,16 +247,16 @@ func TestCheckBlockSanity(t *testing.T) {
0x4e, 0x06, 0xba, 0x64, 0xd7, 0x61, 0xda, 0x25,
0x1a, 0x0e, 0x21, 0xd4, 0x64, 0x49, 0x02, 0xa2,
},
Timestamp: mstime.UnixMilliseconds(0x5cd18053000),
Timestamp: time.Unix(0x5cd18053, 0),
Bits: 0x207fffff,
Nonce: 0x1,
},
Transactions: []*domainmessage.MsgTx{
Transactions: []*wire.MsgTx{
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID{},
Index: 0xffffffff,
},
@@ -246,7 +268,7 @@ func TestCheckBlockSanity(t *testing.T) {
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0x12a05f200, // 5000000000
ScriptPubKey: []byte{
@@ -259,9 +281,9 @@ func TestCheckBlockSanity(t *testing.T) {
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID([32]byte{
0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60,
0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac,
@@ -296,7 +318,7 @@ func TestCheckBlockSanity(t *testing.T) {
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0x2123e300, // 556000000
ScriptPubKey: []byte{
@@ -329,9 +351,9 @@ func TestCheckBlockSanity(t *testing.T) {
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID([32]byte{
0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d,
0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27,
@@ -365,7 +387,7 @@ func TestCheckBlockSanity(t *testing.T) {
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0xf4240, // 1000000
ScriptPubKey: []byte{
@@ -398,9 +420,9 @@ func TestCheckBlockSanity(t *testing.T) {
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID([32]byte{
0x0b, 0x60, 0x72, 0xb3, 0x86, 0xd4, 0xa7, 0x73,
0x23, 0x52, 0x37, 0xf6, 0x4c, 0x11, 0x26, 0xac,
@@ -435,7 +457,7 @@ func TestCheckBlockSanity(t *testing.T) {
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0xf4240, // 1000000
ScriptPubKey: []byte{
@@ -457,7 +479,7 @@ func TestCheckBlockSanity(t *testing.T) {
}
utilInvalidBlock := util.NewBlock(&invalidParentsOrderBlock)
err = dag.checkBlockSanity(utilInvalidBlock, BFNone)
delay, err = dag.checkBlockSanity(utilInvalidBlock, BFNone)
if err == nil {
t.Errorf("CheckBlockSanity: error is nil when it shouldn't be")
}
@@ -467,6 +489,21 @@ func TestCheckBlockSanity(t *testing.T) {
} else if rError.ErrorCode != ErrWrongParentsOrder {
t.Errorf("CheckBlockSanity: Expected error was ErrWrongParentsOrder but got %v", err)
}
if delay != 0 {
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
}
blockInTheFuture := Block100000
expectedDelay := 10 * time.Second
deviationTolerance := time.Duration(dag.TimestampDeviationTolerance*uint64(dag.targetTimePerBlock)) * time.Second
blockInTheFuture.Header.Timestamp = dag.Now().Add(deviationTolerance + expectedDelay)
delay, err = dag.checkBlockSanity(util.NewBlock(&blockInTheFuture), BFNoPoWCheck)
if err != nil {
t.Errorf("CheckBlockSanity: %v", err)
}
if delay != expectedDelay {
t.Errorf("CheckBlockSanity: expected %s delay but got %s", expectedDelay, delay)
}
}
func TestPastMedianTime(t *testing.T) {
@@ -534,15 +571,15 @@ func TestValidateParents(t *testing.T) {
}
defer teardownFunc()
a := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.Params.GenesisBlock)
a := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
b := prepareAndProcessBlockByParentMsgBlocks(t, dag, a)
c := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.Params.GenesisBlock)
c := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
aNode := nodeByMsgBlock(t, dag, a)
bNode := nodeByMsgBlock(t, dag, b)
cNode := nodeByMsgBlock(t, dag, c)
fakeBlockHeader := &domainmessage.BlockHeader{
fakeBlockHeader := &wire.BlockHeader{
HashMerkleRoot: &daghash.ZeroHash,
AcceptedIDMerkleRoot: &daghash.ZeroHash,
UTXOCommitment: &daghash.ZeroHash,
@@ -575,12 +612,13 @@ func TestCheckTransactionSanity(t *testing.T) {
outputValue uint64
nodeSubnetworkID subnetworkid.SubnetworkID
txSubnetworkData *txSubnetworkData
extraModificationsFunc func(*domainmessage.MsgTx)
extraModificationsFunc func(*wire.MsgTx)
expectedErr error
}{
{"good one", 1, 1, 1, *subnetworkid.SubnetworkIDNative, nil, nil, nil},
{"no inputs", 0, 1, 1, *subnetworkid.SubnetworkIDNative, nil, nil, ruleError(ErrNoTxInputs, "")},
{"no outputs", 1, 0, 1, *subnetworkid.SubnetworkIDNative, nil, nil, nil},
{"too massive", 1, 1000000, 1, *subnetworkid.SubnetworkIDNative, nil, nil, ruleError(ErrTxMassTooHigh, "")},
{"too much sompi in one output", 1, 1, util.MaxSompi + 1,
*subnetworkid.SubnetworkIDNative,
nil,
@@ -594,7 +632,7 @@ func TestCheckTransactionSanity(t *testing.T) {
{"duplicate inputs", 2, 1, 1,
*subnetworkid.SubnetworkIDNative,
nil,
func(tx *domainmessage.MsgTx) { tx.TxIn[1].PreviousOutpoint.Index = 0 },
func(tx *wire.MsgTx) { tx.TxIn[1].PreviousOutpoint.Index = 0 },
ruleError(ErrDuplicateTxInputs, "")},
{"1 input coinbase",
1,
@@ -648,14 +686,14 @@ func TestCheckTransactionSanity(t *testing.T) {
{"invalid payload hash", 1, 1, 0,
subnetworkid.SubnetworkID{123},
&txSubnetworkData{&subnetworkid.SubnetworkID{123}, 0, []byte{1}},
func(tx *domainmessage.MsgTx) {
func(tx *wire.MsgTx) {
tx.PayloadHash = &daghash.Hash{}
},
ruleError(ErrInvalidPayloadHash, "")},
{"invalid payload hash in native subnetwork", 1, 1, 0,
*subnetworkid.SubnetworkIDNative,
nil,
func(tx *domainmessage.MsgTx) {
func(tx *wire.MsgTx) {
tx.PayloadHash = daghash.DoubleHashP(tx.Payload)
},
ruleError(ErrInvalidPayloadHash, "")},
@@ -678,8 +716,8 @@ func TestCheckTransactionSanity(t *testing.T) {
// Block100000 defines block 100,000 of the block DAG. It is used to
// test Block operations.
var Block100000 = domainmessage.MsgBlock{
Header: domainmessage.BlockHeader{
var Block100000 = wire.MsgBlock{
Header: wire.BlockHeader{
Version: 0x10000000,
ParentHashes: []*daghash.Hash{
{
@@ -708,16 +746,16 @@ var Block100000 = domainmessage.MsgBlock{
0x3c, 0xb1, 0x16, 0x8f, 0x5f, 0x6b, 0x45, 0x87,
},
UTXOCommitment: &daghash.ZeroHash,
Timestamp: mstime.UnixMilliseconds(0x17305aa654a),
Timestamp: time.Unix(0x5cdac4b1, 0),
Bits: 0x207fffff,
Nonce: 1,
Nonce: 0x00000001,
},
Transactions: []*domainmessage.MsgTx{
Transactions: []*wire.MsgTx{
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID{
0x9b, 0x22, 0x59, 0x44, 0x66, 0xf0, 0xbe, 0x50,
0x7c, 0x1c, 0x8a, 0xf6, 0x06, 0x27, 0xe6, 0x33,
@@ -730,7 +768,7 @@ var Block100000 = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0x12a05f200, // 5000000000
ScriptPubKey: []byte{
@@ -752,9 +790,9 @@ var Block100000 = domainmessage.MsgBlock{
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID{
0x16, 0x5e, 0x38, 0xe8, 0xb3, 0x91, 0x45, 0x95,
0xd9, 0xc6, 0x41, 0xf3, 0xb8, 0xee, 0xc2, 0xf3,
@@ -766,7 +804,7 @@ var Block100000 = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID{
0x4b, 0xb0, 0x75, 0x35, 0xdf, 0xd5, 0x8e, 0x0b,
0x3c, 0xd6, 0x4f, 0xd7, 0x15, 0x52, 0x80, 0x87,
@@ -782,9 +820,9 @@ var Block100000 = domainmessage.MsgBlock{
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID([32]byte{
0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60,
0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac,
@@ -819,7 +857,7 @@ var Block100000 = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0x2123e300, // 556000000
ScriptPubKey: []byte{
@@ -852,9 +890,9 @@ var Block100000 = domainmessage.MsgBlock{
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID([32]byte{
0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d,
0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27,
@@ -888,7 +926,7 @@ var Block100000 = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0xf4240, // 1000000
ScriptPubKey: []byte{
@@ -921,9 +959,9 @@ var Block100000 = domainmessage.MsgBlock{
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID([32]byte{
0x0b, 0x60, 0x72, 0xb3, 0x86, 0xd4, 0xa7, 0x73,
0x23, 0x52, 0x37, 0xf6, 0x4c, 0x11, 0x26, 0xac,
@@ -958,7 +996,7 @@ var Block100000 = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0xf4240, // 1000000
ScriptPubKey: []byte{
@@ -980,8 +1018,8 @@ var Block100000 = domainmessage.MsgBlock{
}
// BlockWithWrongTxOrder defines invalid block 100,000 of the block DAG.
var BlockWithWrongTxOrder = domainmessage.MsgBlock{
Header: domainmessage.BlockHeader{
var BlockWithWrongTxOrder = wire.MsgBlock{
Header: wire.BlockHeader{
Version: 1,
ParentHashes: []*daghash.Hash{
{
@@ -1015,16 +1053,16 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
0x0b, 0x79, 0xf5, 0x29, 0x6d, 0x1c, 0xaa, 0x90,
0x2f, 0x01, 0xd4, 0x83, 0x9b, 0x2a, 0x04, 0x5e,
},
Timestamp: mstime.UnixMilliseconds(0x5cd16eaa000),
Timestamp: time.Unix(0x5cd16eaa, 0),
Bits: 0x207fffff,
Nonce: 1,
Nonce: 0x0,
},
Transactions: []*domainmessage.MsgTx{
Transactions: []*wire.MsgTx{
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID{
0x9b, 0x22, 0x59, 0x44, 0x66, 0xf0, 0xbe, 0x50,
0x7c, 0x1c, 0x8a, 0xf6, 0x06, 0x27, 0xe6, 0x33,
@@ -1037,7 +1075,7 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0x12a05f200, // 5000000000
ScriptPubKey: []byte{
@@ -1059,9 +1097,9 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID{
0x16, 0x5e, 0x38, 0xe8, 0xb3, 0x91, 0x45, 0x95,
0xd9, 0xc6, 0x41, 0xf3, 0xb8, 0xee, 0xc2, 0xf3,
@@ -1073,7 +1111,7 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID{
0x4b, 0xb0, 0x75, 0x35, 0xdf, 0xd5, 0x8e, 0x0b,
0x3c, 0xd6, 0x4f, 0xd7, 0x15, 0x52, 0x80, 0x87,
@@ -1089,9 +1127,9 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID([32]byte{
0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60,
0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac,
@@ -1126,7 +1164,7 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0x2123e300, // 556000000
ScriptPubKey: []byte{
@@ -1161,9 +1199,9 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID([32]byte{
0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d,
0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27,
@@ -1197,7 +1235,7 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0xf4240, // 1000000
ScriptPubKey: []byte{
@@ -1230,9 +1268,9 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
},
{
Version: 1,
TxIn: []*domainmessage.TxIn{
TxIn: []*wire.TxIn{
{
PreviousOutpoint: domainmessage.Outpoint{
PreviousOutpoint: wire.Outpoint{
TxID: daghash.TxID([32]byte{
0x0b, 0x60, 0x72, 0xb3, 0x86, 0xd4, 0xa7, 0x73,
0x23, 0x52, 0x37, 0xf6, 0x4c, 0x11, 0x26, 0xac,
@@ -1267,7 +1305,7 @@ var BlockWithWrongTxOrder = domainmessage.MsgBlock{
Sequence: math.MaxUint64,
},
},
TxOut: []*domainmessage.TxOut{
TxOut: []*wire.TxOut{
{
Value: 0xf4240, // 1000000
ScriptPubKey: []byte{

294
blockdag/versionbits.go Normal file
View File

@@ -0,0 +1,294 @@
// Copyright (c) 2016-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 (
"math"
"github.com/kaspanet/kaspad/dagconfig"
)
const (
// vbTopBits defines the bits to set in the version to signal that the
// version bits scheme is being used.
vbTopBits = 0x10000000
// vbTopMask is the bitmask to use to determine whether or not the
// version bits scheme is in use.
vbTopMask = 0xe0000000
// vbNumBits is the total number of bits available for use with the
// version bits scheme.
vbNumBits = 29
// unknownVerNumToCheck is the number of previous blocks to consider
// when checking for a threshold of unknown block versions for the
// purposes of warning the user.
unknownVerNumToCheck = 100
// unknownVerWarnNum is the threshold of previous blocks that have an
// unknown version to use for the purposes of warning the user.
unknownVerWarnNum = unknownVerNumToCheck / 2
)
// bitConditionChecker provides a thresholdConditionChecker which can be used to
// test whether or not a specific bit is set when it's not supposed to be
// according to the expected version based on the known deployments and the
// current state of the DAG. This is useful for detecting and warning about
// unknown rule activations.
type bitConditionChecker struct {
bit uint32
dag *BlockDAG
}
// Ensure the bitConditionChecker type implements the thresholdConditionChecker
// interface.
var _ thresholdConditionChecker = bitConditionChecker{}
// BeginTime returns the unix timestamp for the median block time after which
// voting on a rule change starts (at the next window).
//
// Since this implementation checks for unknown rules, it returns 0 so the rule
// is always treated as active.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) BeginTime() uint64 {
return 0
}
// EndTime returns the unix timestamp for the median block time after which an
// attempted rule change fails if it has not already been locked in or
// activated.
//
// Since this implementation checks for unknown rules, it returns the maximum
// possible timestamp so the rule is always treated as active.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) EndTime() uint64 {
return math.MaxUint64
}
// RuleChangeActivationThreshold is the number of blocks for which the condition
// must be true in order to lock in a rule change.
//
// This implementation returns the value defined by the DAG params the checker
// is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) RuleChangeActivationThreshold() uint64 {
return c.dag.dagParams.RuleChangeActivationThreshold
}
// MinerConfirmationWindow is the number of blocks in each threshold state
// retarget window.
//
// This implementation returns the value defined by the DAG params the checker
// is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) MinerConfirmationWindow() uint64 {
return c.dag.dagParams.MinerConfirmationWindow
}
// Condition returns true when the specific bit associated with the checker is
// set and it's not supposed to be according to the expected version based on
// the known deployments and the current state of the DAG.
//
// This function MUST be called with the DAG state lock held (for writes).
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) Condition(node *blockNode) (bool, error) {
conditionMask := uint32(1) << c.bit
version := uint32(node.version)
if version&vbTopMask != vbTopBits {
return false, nil
}
if version&conditionMask == 0 {
return false, nil
}
expectedVersion, err := c.dag.calcNextBlockVersion(node.selectedParent)
if err != nil {
return false, err
}
return uint32(expectedVersion)&conditionMask == 0, nil
}
// deploymentChecker provides a thresholdConditionChecker which can be used to
// test a specific deployment rule. This is required for properly detecting
// and activating consensus rule changes.
type deploymentChecker struct {
deployment *dagconfig.ConsensusDeployment
dag *BlockDAG
}
// Ensure the deploymentChecker type implements the thresholdConditionChecker
// interface.
var _ thresholdConditionChecker = deploymentChecker{}
// BeginTime returns the unix timestamp for the median block time after which
// voting on a rule change starts (at the next window).
//
// This implementation returns the value defined by the specific deployment the
// checker is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) BeginTime() uint64 {
return c.deployment.StartTime
}
// EndTime returns the unix timestamp for the median block time after which an
// attempted rule change fails if it has not already been locked in or
// activated.
//
// This implementation returns the value defined by the specific deployment the
// checker is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) EndTime() uint64 {
return c.deployment.ExpireTime
}
// RuleChangeActivationThreshold is the number of blocks for which the condition
// must be true in order to lock in a rule change.
//
// This implementation returns the value defined by the DAG params the checker
// is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) RuleChangeActivationThreshold() uint64 {
return c.dag.dagParams.RuleChangeActivationThreshold
}
// MinerConfirmationWindow is the number of blocks in each threshold state
// retarget window.
//
// This implementation returns the value defined by the DAG params the checker
// is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) MinerConfirmationWindow() uint64 {
return c.dag.dagParams.MinerConfirmationWindow
}
// Condition returns true when the specific bit defined by the deployment
// associated with the checker is set.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) Condition(node *blockNode) (bool, error) {
conditionMask := uint32(1) << c.deployment.BitNumber
version := uint32(node.version)
return (version&vbTopMask == vbTopBits) && (version&conditionMask != 0),
nil
}
// calcNextBlockVersion calculates the expected version of the block after the
// passed previous block node based on the state of started and locked in
// rule change deployments.
//
// This function differs from the exported CalcNextBlockVersion in that the
// exported version uses the selected tip as the previous block node
// while this function accepts any block node.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) calcNextBlockVersion(prevNode *blockNode) (int32, error) {
// Set the appropriate bits for each actively defined rule deployment
// that is either in the process of being voted on, or locked in for the
// activation at the next threshold window change.
expectedVersion := uint32(vbTopBits)
for id := 0; id < len(dag.dagParams.Deployments); id++ {
deployment := &dag.dagParams.Deployments[id]
cache := &dag.deploymentCaches[id]
checker := deploymentChecker{deployment: deployment, dag: dag}
state, err := dag.thresholdState(prevNode, checker, cache)
if err != nil {
return 0, err
}
if state == ThresholdStarted || state == ThresholdLockedIn {
expectedVersion |= uint32(1) << deployment.BitNumber
}
}
return int32(expectedVersion), nil
}
// CalcNextBlockVersion calculates the expected version of the block after the
// end of the current selected tip based on the state of started and locked in
// rule change deployments.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) CalcNextBlockVersion() (int32, error) {
version, err := dag.calcNextBlockVersion(dag.selectedTip())
return version, err
}
// warnUnknownRuleActivations displays a warning when any unknown new rules are
// either about to activate or have been activated. This will only happen once
// when new rules have been activated and every block for those about to be
// activated.
//
// This function MUST be called with the DAG state lock held (for writes)
func (dag *BlockDAG) warnUnknownRuleActivations(node *blockNode) error {
// Warn if any unknown new rules are either about to activate or have
// already been activated.
for bit := uint32(0); bit < vbNumBits; bit++ {
checker := bitConditionChecker{bit: bit, dag: dag}
cache := &dag.warningCaches[bit]
state, err := dag.thresholdState(node.selectedParent, checker, cache)
if err != nil {
return err
}
switch state {
case ThresholdActive:
if !dag.unknownRulesWarned {
log.Warnf("Unknown new rules activated (bit %d)",
bit)
dag.unknownRulesWarned = true
}
case ThresholdLockedIn:
window := checker.MinerConfirmationWindow()
activationBlueScore := window - (node.blueScore % window)
log.Warnf("Unknown new rules are about to activate in "+
"%d blueScore (bit %d)", activationBlueScore, bit)
}
}
return nil
}
// warnUnknownVersions logs a warning if a high enough percentage of the last
// blocks have unexpected versions.
//
// This function MUST be called with the DAG state lock held (for writes)
func (dag *BlockDAG) warnUnknownVersions(node *blockNode) error {
// Nothing to do if already warned.
if dag.unknownVersionsWarned {
return nil
}
// Warn if enough previous blocks have unexpected versions.
numUpgraded := uint32(0)
for i := uint32(0); i < unknownVerNumToCheck && node != nil; i++ {
expectedVersion, err := dag.calcNextBlockVersion(node.selectedParent)
if err != nil {
return err
}
if (node.version & ^expectedVersion) != 0 {
numUpgraded++
}
node = node.selectedParent
}
if numUpgraded > unknownVerWarnNum {
log.Warn("Unknown block versions are being mined, so new " +
"rules might be in effect. Are you running the " +
"latest version of the software?")
dag.unknownVersionsWarned = true
}
return nil
}

View File

@@ -5,7 +5,7 @@
package blockdag
import (
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util"
"reflect"
"testing"
@@ -97,7 +97,7 @@ func TestVirtualBlock(t *testing.T) {
tipsToSet: []*blockNode{},
tipsToAdd: []*blockNode{node0, node1, node2, node3, node4, node5, node6},
expectedTips: blockSetFromSlice(node2, node5, node6),
expectedSelectedParent: node5,
expectedSelectedParent: node6,
},
}

View File

@@ -8,8 +8,8 @@ import (
"os"
"runtime"
"github.com/kaspanet/kaspad/infrastructure/limits"
"github.com/kaspanet/kaspad/infrastructure/logs"
"github.com/kaspanet/kaspad/limits"
"github.com/kaspanet/kaspad/logs"
"github.com/kaspanet/kaspad/util/panics"
)
@@ -21,7 +21,7 @@ const (
var (
cfg *ConfigFlags
log *logs.Logger
spawn func(string, func())
spawn func(func())
)
// realMain is the real main function for the utility. It is necessary to work

View File

@@ -7,7 +7,7 @@ package main
import (
"fmt"
flags "github.com/jessevdk/go-flags"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/config"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"os"

View File

@@ -6,16 +6,15 @@ package main
import (
"encoding/binary"
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/blockdag/indexers"
"github.com/pkg/errors"
"io"
"sync"
"time"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/wire"
)
// importResults houses the stats and result as an import operation.
@@ -40,8 +39,8 @@ type blockImporter struct {
receivedLogBlocks int64
receivedLogTx int64
lastHeight int64
lastBlockTime mstime.Time
lastLogTime mstime.Time
lastBlockTime time.Time
lastLogTime time.Time
}
// readBlock reads the next block from the input file.
@@ -68,10 +67,10 @@ func (bi *blockImporter) readBlock() ([]byte, error) {
if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
return nil, err
}
if blockLen > domainmessage.MaxMessagePayload {
if blockLen > wire.MaxMessagePayload {
return nil, errors.Errorf("block payload of %d bytes is larger "+
"than the max allowed %d bytes", blockLen,
domainmessage.MaxMessagePayload)
wire.MaxMessagePayload)
}
serializedBlock := make([]byte, blockLen)
@@ -171,7 +170,7 @@ out:
func (bi *blockImporter) logProgress() {
bi.receivedLogBlocks++
now := mstime.Now()
now := time.Now()
duration := now.Sub(bi.lastLogTime)
if duration < time.Second*time.Duration(cfg.Progress) {
return
@@ -265,12 +264,12 @@ func (bi *blockImporter) Import() chan *importResults {
// Start up the read and process handling goroutines. This setup allows
// blocks to be read from disk in parallel while being processed.
bi.wg.Add(2)
spawn("blockImporter.readHandler", bi.readHandler)
spawn("blockImporter.processHandler", bi.processHandler)
spawn(bi.readHandler)
spawn(bi.processHandler)
// Wait for the import to finish in a separate goroutine and signal
// the status handler when done.
spawn("blockImporter.sendToDoneChan", func() {
spawn(func() {
bi.wg.Wait()
bi.doneChan <- true
})
@@ -278,7 +277,7 @@ func (bi *blockImporter) Import() chan *importResults {
// Start the status handler and return the result channel that it will
// send the results on when the import is done.
resultChan := make(chan *importResults)
spawn("blockImporter.statusHandler", func() {
spawn(func() {
bi.statusHandler(resultChan)
})
return resultChan
@@ -316,6 +315,6 @@ func newBlockImporter(r io.ReadSeeker) (*blockImporter, error) {
errChan: make(chan error),
quit: make(chan struct{}),
dag: dag,
lastLogTime: mstime.Now(),
lastLogTime: time.Now(),
}, nil
}

View File

@@ -6,7 +6,7 @@ package main
import (
"fmt"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/config"
"github.com/kaspanet/kaspad/version"
"github.com/pkg/errors"
"io/ioutil"
@@ -16,7 +16,7 @@ import (
"strings"
"github.com/jessevdk/go-flags"
"github.com/kaspanet/kaspad/network/rpc/model"
"github.com/kaspanet/kaspad/rpcmodel"
"github.com/kaspanet/kaspad/util"
)
@@ -24,7 +24,7 @@ const (
// unusableFlags are the command usage flags which this utility are not
// able to use. In particular it doesn't support websockets and
// consequently notifications.
unusableFlags = model.UFWebsocketOnly | model.UFNotification
unusableFlags = rpcmodel.UFWebsocketOnly | rpcmodel.UFNotification
)
var (
@@ -45,10 +45,10 @@ func listCommands() {
)
// Get a list of registered commands and categorize and filter them.
cmdMethods := model.RegisteredCmdMethods()
cmdMethods := rpcmodel.RegisteredCmdMethods()
categorized := make([][]string, numCategories)
for _, method := range cmdMethods {
flags, err := model.MethodUsageFlags(method)
flags, err := rpcmodel.MethodUsageFlags(method)
if err != nil {
// This should never happen since the method was just
// returned from the package, but be safe.
@@ -60,7 +60,7 @@ func listCommands() {
continue
}
usage, err := model.MethodUsageText(method)
usage, err := rpcmodel.MethodUsageText(method)
if err != nil {
// This should never happen since the method was just
// returned from the package, but be safe.

View File

@@ -11,7 +11,7 @@ import (
"net/http"
"github.com/btcsuite/go-socks/socks"
"github.com/kaspanet/kaspad/network/rpc/model"
"github.com/kaspanet/kaspad/rpcmodel"
)
// newHTTPClient returns a new HTTP client that is configured according to the
@@ -117,7 +117,7 @@ func sendPostRequest(marshalledJSON []byte, cfg *ConfigFlags) ([]byte, error) {
}
// Unmarshal the response.
var resp model.Response
var resp rpcmodel.Response
if err := json.Unmarshal(respBytes, &resp); err != nil {
return nil, err
}

View File

@@ -11,7 +11,7 @@ import (
"path/filepath"
"strings"
"github.com/kaspanet/kaspad/network/rpc/model"
"github.com/kaspanet/kaspad/rpcmodel"
)
const (
@@ -21,7 +21,7 @@ const (
// commandUsage display the usage for a specific command.
func commandUsage(method string) {
usage, err := model.MethodUsageText(method)
usage, err := rpcmodel.MethodUsageText(method)
if err != nil {
// This should never happen since the method was already checked
// before calling this function, but be safe.
@@ -60,7 +60,7 @@ func main() {
// Ensure the specified method identifies a valid registered command and
// is one of the usable types.
method := args[0]
usageFlags, err := model.MethodUsageFlags(method)
usageFlags, err := rpcmodel.MethodUsageFlags(method)
if err != nil {
fmt.Fprintf(os.Stderr, "Unrecognized command '%s'\n", method)
fmt.Fprintln(os.Stderr, listCmdMessage)
@@ -105,13 +105,13 @@ func main() {
// Attempt to create the appropriate command using the arguments
// provided by the user.
cmd, err := model.NewCommand(method, params...)
cmd, err := rpcmodel.NewCommand(method, params...)
if err != nil {
// Show the error along with its error code when it's a
// model.Error as it reallistcally will always be since the
// rpcmodel.Error as it reallistcally will always be since the
// NewCommand function is only supposed to return errors of that
// type.
var rpcModelErr model.Error
var rpcModelErr rpcmodel.Error
if ok := errors.As(err, &rpcModelErr); ok {
fmt.Fprintf(os.Stderr, "%s error: %s (command code: %s)\n",
method, err, rpcModelErr.ErrorCode)
@@ -119,7 +119,7 @@ func main() {
os.Exit(1)
}
// The error is not a model.Error and this really should not
// The error is not a rpcmodel.Error and this really should not
// happen. Nevertheless, fallback to just showing the error
// if it should happen due to a bug in the package.
fmt.Fprintf(os.Stderr, "%s error: %s\n", method, err)
@@ -129,7 +129,7 @@ func main() {
// Marshal the command into a JSON-RPC byte slice in preparation for
// sending it to the RPC server.
marshalledJSON, err := model.MarshalCommand(1, cmd)
marshalledJSON, err := rpcmodel.MarshalCommand(1, cmd)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)

View File

@@ -1,39 +1,39 @@
package main
import (
"github.com/kaspanet/kaspad/network/domainmessage"
"github.com/kaspanet/kaspad/network/rpc/client"
"github.com/kaspanet/kaspad/rpcclient"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/wire"
"github.com/pkg/errors"
"io/ioutil"
"time"
)
type minerClient struct {
*client.Client
*rpcclient.Client
onBlockAdded chan struct{}
}
func newMinerClient(connCfg *client.ConnConfig) (*minerClient, error) {
minerClient := &minerClient{
func newMinerClient(connCfg *rpcclient.ConnConfig) (*minerClient, error) {
client := &minerClient{
onBlockAdded: make(chan struct{}, 1),
}
notificationHandlers := &client.NotificationHandlers{
OnFilteredBlockAdded: func(_ uint64, header *domainmessage.BlockHeader,
notificationHandlers := &rpcclient.NotificationHandlers{
OnFilteredBlockAdded: func(_ uint64, header *wire.BlockHeader,
txs []*util.Tx) {
minerClient.onBlockAdded <- struct{}{}
client.onBlockAdded <- struct{}{}
},
}
var err error
minerClient.Client, err = client.New(connCfg, notificationHandlers)
client.Client, err = rpcclient.New(connCfg, notificationHandlers)
if err != nil {
return nil, errors.Errorf("Error connecting to address %s: %s", connCfg.Host, err)
}
if err = minerClient.NotifyBlocks(); err != nil {
return nil, errors.Wrapf(err, "error while registering minerClient %s for block notifications", minerClient.Host())
if err = client.NotifyBlocks(); err != nil {
return nil, errors.Errorf("Error while registering client %s for block notifications: %s", client.Host(), err)
}
return minerClient, nil
return client, nil
}
func connectToServer(cfg *configFlags) (*minerClient, error) {
@@ -47,7 +47,7 @@ func connectToServer(cfg *configFlags) (*minerClient, error) {
return nil, err
}
connCfg := &client.ConnConfig{
connCfg := &rpcclient.ConnConfig{
Host: rpcAddr,
Endpoint: "ws",
User: cfg.RPCUser,

View File

@@ -7,7 +7,7 @@ import (
"strconv"
"strings"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/config"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
@@ -36,7 +36,6 @@ type configFlags struct {
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
DisableTLS bool `long:"notls" description:"Disable TLS"`
MiningAddr string `long:"miningaddr" description:"Address to mine to"`
Verbose bool `long:"verbose" short:"v" description:"Enable logging of RPC requests"`
NumberOfBlocks uint64 `short:"n" long:"numblocks" description:"Number of blocks to mine. If omitted, will mine until the process is interrupted."`
BlockDelay uint64 `long:"block-delay" description:"Delay for block submission (in milliseconds). This is used only for testing purposes."`

View File

@@ -2,8 +2,8 @@ package main
import (
"fmt"
"github.com/kaspanet/kaspad/infrastructure/logs"
"github.com/kaspanet/kaspad/network/rpc/client"
"github.com/kaspanet/kaspad/logs"
"github.com/kaspanet/kaspad/rpcclient"
"github.com/kaspanet/kaspad/util/panics"
"os"
)
@@ -28,5 +28,5 @@ func initLog(logFile, errLogFile string) {
}
func enableRPCLogging() {
client.UseLogger(backendLog, logs.LevelTrace)
rpcclient.UseLogger(backendLog, logs.LevelTrace)
}

View File

@@ -2,7 +2,6 @@ package main
import (
"fmt"
"github.com/kaspanet/kaspad/util"
"os"
"github.com/kaspanet/kaspad/version"
@@ -11,13 +10,13 @@ import (
_ "net/http/pprof"
"github.com/kaspanet/kaspad/infrastructure/signal"
"github.com/kaspanet/kaspad/signal"
"github.com/kaspanet/kaspad/util/panics"
"github.com/kaspanet/kaspad/util/profiling"
)
func main() {
defer panics.HandlePanic(log, "MAIN", nil)
defer panics.HandlePanic(log, nil)
interrupt := signal.InterruptListener()
cfg, err := parseConfig()
@@ -40,20 +39,15 @@ func main() {
client, err := connectToServer(cfg)
if err != nil {
panic(errors.Wrap(err, "error connecting to the RPC server"))
panic(errors.Wrap(err, "Error connecting to the RPC server"))
}
defer client.Disconnect()
miningAddr, err := util.DecodeAddress(cfg.MiningAddr, cfg.ActiveNetParams.Prefix)
if err != nil {
panic(errors.Wrap(err, "error decoding mining address"))
}
doneChan := make(chan struct{})
spawn("mineLoop", func() {
err = mineLoop(client, cfg.NumberOfBlocks, cfg.BlockDelay, cfg.MineWhenNotSynced, miningAddr)
spawn(func() {
err = mineLoop(client, cfg.NumberOfBlocks, cfg.BlockDelay, cfg.MineWhenNotSynced)
if err != nil {
panic(errors.Wrap(err, "error in mine loop"))
panic(errors.Errorf("Error in mine loop: %s", err))
}
doneChan <- struct{}{}
})

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