mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-23 03:48:20 +00:00
Compare commits
49 Commits
v0.1.1-dev
...
v0.1.2-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34fb066590 | ||
|
|
299826f392 | ||
|
|
3d8dd8724d | ||
|
|
b8a00f7519 | ||
|
|
4dfc8cf5b0 | ||
|
|
5a99e4d2f3 | ||
|
|
606cd668ff | ||
|
|
dd537f5143 | ||
|
|
a1c631be62 | ||
|
|
707a728656 | ||
|
|
80b5631a48 | ||
|
|
2373965551 | ||
|
|
65cbb6655b | ||
|
|
cdd96d0670 | ||
|
|
ad04bbde83 | ||
|
|
5374d95416 | ||
|
|
de9aa39cc5 | ||
|
|
98987f4a8f | ||
|
|
9745f31b69 | ||
|
|
ee08531a52 | ||
|
|
61baf7b260 | ||
|
|
650e4f735e | ||
|
|
550b12b041 | ||
|
|
a4bb070722 | ||
|
|
30fe0c279b | ||
|
|
e405dd5981 | ||
|
|
243b4b8021 | ||
|
|
dd4c93e1ef | ||
|
|
a07335d74d | ||
|
|
7567cd4cb9 | ||
|
|
51ff9e2562 | ||
|
|
5b8ab63890 | ||
|
|
3dd7dc4496 | ||
|
|
d90a08ecfa | ||
|
|
45dc1a3e7b | ||
|
|
4ffb5daa37 | ||
|
|
b9138b720d | ||
|
|
d8954f1339 | ||
|
|
eb953286ec | ||
|
|
41c8178ad3 | ||
|
|
aa74b51e6f | ||
|
|
f7800eb5c4 | ||
|
|
44c55900f8 | ||
|
|
4c0ea78026 | ||
|
|
03a93fe51e | ||
|
|
eca0514465 | ||
|
|
5daab45947 | ||
|
|
25bdaeed31 | ||
|
|
a3dc2f7da7 |
@@ -4,7 +4,7 @@ Kaspad
|
||||
Warning: This is pre-alpha software. There's no guarantee anything works.
|
||||
====
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad)
|
||||
|
||||
Kaspad is the reference full node Kaspa implementation written in Go (golang).
|
||||
@@ -75,5 +75,5 @@ The documentation is a work-in-progress. It is located in the [docs](https://git
|
||||
|
||||
## License
|
||||
|
||||
Kaspad is licensed under the [copyfree](http://copyfree.org) ISC License.
|
||||
Kaspad is licensed under the copyfree [ISC License](https://choosealicense.com/licenses/isc/).
|
||||
|
||||
|
||||
@@ -717,11 +717,7 @@ func (a *AddrManager) Start() {
|
||||
|
||||
// Start the address ticker to save addresses periodically.
|
||||
a.wg.Add(1)
|
||||
spawn(a.addressHandler, a.handlePanic)
|
||||
}
|
||||
|
||||
func (a *AddrManager) handlePanic() {
|
||||
atomic.AddInt32(&a.shutdown, 1)
|
||||
spawn(a.addressHandler)
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down the address manager by stopping the main handler.
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
package addrmgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -108,6 +114,551 @@ func TestStartStop(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAddressByIP(t *testing.T) {
|
||||
originalActiveCfg := config.ActiveConfig()
|
||||
config.SetActiveConfig(&config.Config{
|
||||
Flags: &config.Flags{
|
||||
NetworkFlags: config.NetworkFlags{
|
||||
ActiveNetParams: &dagconfig.SimnetParams},
|
||||
},
|
||||
})
|
||||
defer config.SetActiveConfig(originalActiveCfg)
|
||||
|
||||
fmtErr := errors.Errorf("")
|
||||
addrErr := &net.AddrError{}
|
||||
var tests = []struct {
|
||||
addrIP string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
someIP + ":16111",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
someIP,
|
||||
addrErr,
|
||||
},
|
||||
{
|
||||
someIP[:12] + ":8333",
|
||||
fmtErr,
|
||||
},
|
||||
{
|
||||
someIP + ":abcd",
|
||||
fmtErr,
|
||||
},
|
||||
}
|
||||
|
||||
amgr := New("testaddressbyip", nil, nil)
|
||||
for i, test := range tests {
|
||||
err := amgr.AddAddressByIP(test.addrIP, nil)
|
||||
if test.err != nil && err == nil {
|
||||
t.Errorf("TestAddAddressByIP test %d failed expected an error and got none", i)
|
||||
continue
|
||||
}
|
||||
if test.err == nil && err != nil {
|
||||
t.Errorf("TestAddAddressByIP test %d failed expected no error and got one", i)
|
||||
continue
|
||||
}
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("TestAddAddressByIP test %d failed got %v, want %v", i,
|
||||
reflect.TypeOf(err), reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 wire.NetAddress
|
||||
priority AddressPriority
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
wire.NetAddress{IP: net.ParseIP("192.168.0.100")},
|
||||
InterfacePrio,
|
||||
false,
|
||||
},
|
||||
{
|
||||
wire.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
||||
InterfacePrio,
|
||||
true,
|
||||
},
|
||||
{
|
||||
wire.NetAddress{IP: net.ParseIP("204.124.1.1")},
|
||||
BoundPrio,
|
||||
true,
|
||||
},
|
||||
{
|
||||
wire.NetAddress{IP: net.ParseIP("::1")},
|
||||
InterfacePrio,
|
||||
false,
|
||||
},
|
||||
{
|
||||
wire.NetAddress{IP: net.ParseIP("fe80::1")},
|
||||
InterfacePrio,
|
||||
false,
|
||||
},
|
||||
{
|
||||
wire.NetAddress{IP: net.ParseIP("2620:100::1")},
|
||||
InterfacePrio,
|
||||
true,
|
||||
},
|
||||
}
|
||||
amgr := New("testaddlocaladdress", nil, nil)
|
||||
for x, test := range tests {
|
||||
result := amgr.AddLocalAddress(&test.address, test.priority)
|
||||
if result == nil && !test.valid {
|
||||
t.Errorf("TestAddLocalAddress test #%d failed: %s should have "+
|
||||
"been accepted", x, test.address.IP)
|
||||
continue
|
||||
}
|
||||
if result != nil && test.valid {
|
||||
t.Errorf("TestAddLocalAddress test #%d failed: %s should not have "+
|
||||
"been accepted", x, test.address.IP)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttempt(t *testing.T) {
|
||||
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 := n.AddAddressByIP(someIP+":8333", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
ka := n.GetAddress()
|
||||
|
||||
if !ka.LastAttempt().IsZero() {
|
||||
t.Errorf("Address should not have attempts, but does")
|
||||
}
|
||||
|
||||
na := ka.NetAddress()
|
||||
n.Attempt(na)
|
||||
|
||||
if ka.LastAttempt().IsZero() {
|
||||
t.Errorf("Address should have an attempt, but does not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnected(t *testing.T) {
|
||||
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 := n.AddAddressByIP(someIP+":8333", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
ka := n.GetAddress()
|
||||
na := ka.NetAddress()
|
||||
// make it an hour ago
|
||||
na.Timestamp = time.Unix(time.Now().Add(time.Hour*-1).Unix(), 0)
|
||||
|
||||
n.Connected(na)
|
||||
|
||||
if !ka.NetAddress().Timestamp.After(na.Timestamp) {
|
||||
t.Errorf("Address should have a new timestamp, but does not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNeedMoreAddresses(t *testing.T) {
|
||||
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 := n.NeedMoreAddresses()
|
||||
if !b {
|
||||
t.Errorf("Expected that we need more addresses")
|
||||
}
|
||||
addrs := make([]*wire.NetAddress, addrsToAdd)
|
||||
|
||||
var err error
|
||||
for i := 0; i < addrsToAdd; i++ {
|
||||
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 := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
|
||||
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 = n.NeedMoreAddresses()
|
||||
if b {
|
||||
t.Errorf("Expected that we don't need more addresses")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGood(t *testing.T) {
|
||||
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([]*wire.NetAddress, addrsToAdd)
|
||||
subnetworkCount := 32
|
||||
subnetworkIDs := make([]*subnetworkid.SubnetworkID, subnetworkCount)
|
||||
|
||||
var err error
|
||||
for i := 0; i < addrsToAdd; i++ {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < subnetworkCount; i++ {
|
||||
subnetworkIDs[i] = &subnetworkid.SubnetworkID{0xff - byte(i)}
|
||||
}
|
||||
|
||||
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
|
||||
n.AddAddresses(addrs, srcAddr, nil)
|
||||
for i, addr := range addrs {
|
||||
n.Good(addr, subnetworkIDs[i%subnetworkCount])
|
||||
}
|
||||
|
||||
numAddrs := n.TotalNumAddresses()
|
||||
if numAddrs >= addrsToAdd {
|
||||
t.Errorf("Number of addresses is too many: %d vs %d", numAddrs, addrsToAdd)
|
||||
}
|
||||
|
||||
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(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoodChangeSubnetworkID(t *testing.T) {
|
||||
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 := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
|
||||
oldSubnetwork := subnetworkid.SubnetworkIDNative
|
||||
n.AddAddress(addr, srcAddr, oldSubnetwork)
|
||||
n.Good(addr, oldSubnetwork)
|
||||
|
||||
// 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")
|
||||
}
|
||||
if !ka.SubnetworkID().IsEqual(oldSubnetwork) {
|
||||
t.Fatalf("Address index did not point to oldSubnetwork")
|
||||
}
|
||||
|
||||
// make sure address was added to correct bucket under oldSubnetwork
|
||||
bucket := n.addrTried[*oldSubnetwork][n.getTriedBucket(addr)]
|
||||
wasFound := false
|
||||
for e := bucket.Front(); e != nil; e = e.Next() {
|
||||
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
|
||||
wasFound = true
|
||||
}
|
||||
}
|
||||
if !wasFound {
|
||||
t.Fatalf("Address was not found in the correct bucket in oldSubnetwork")
|
||||
}
|
||||
|
||||
// now call .Good again with a different subnetwork
|
||||
newSubnetwork := subnetworkid.SubnetworkIDRegistry
|
||||
n.Good(addr, newSubnetwork)
|
||||
|
||||
// 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")
|
||||
}
|
||||
if !ka.SubnetworkID().IsEqual(newSubnetwork) {
|
||||
t.Fatalf("Address index did not point to newSubnetwork")
|
||||
}
|
||||
|
||||
// make sure address was removed from bucket under oldSubnetwork
|
||||
bucket = n.addrTried[*oldSubnetwork][n.getTriedBucket(addr)]
|
||||
wasFound = false
|
||||
for e := bucket.Front(); e != nil; e = e.Next() {
|
||||
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
|
||||
wasFound = true
|
||||
}
|
||||
}
|
||||
if wasFound {
|
||||
t.Fatalf("Address was not removed from bucket in oldSubnetwork")
|
||||
}
|
||||
|
||||
// make sure address was added to correct bucket under newSubnetwork
|
||||
bucket = n.addrTried[*newSubnetwork][n.getTriedBucket(addr)]
|
||||
wasFound = false
|
||||
for e := bucket.Front(); e != nil; e = e.Next() {
|
||||
if NetAddressKey(e.Value.(*KnownAddress).NetAddress()) == addrKey {
|
||||
wasFound = true
|
||||
}
|
||||
}
|
||||
if !wasFound {
|
||||
t.Fatalf("Address was not found in the correct bucket in newSubnetwork")
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
n := New("testgetaddress", lookupFunc, localSubnetworkID)
|
||||
|
||||
// Get an address from an empty set (should error)
|
||||
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 := n.AddAddressByIP(someIP+":8332", localSubnetworkID)
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
ka := n.GetAddress()
|
||||
if ka == nil {
|
||||
t.Fatalf("Did not get an address where there is one in the pool")
|
||||
}
|
||||
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}
|
||||
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 := n.TotalNumAddresses()
|
||||
if numAddrs != 1 {
|
||||
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
|
||||
}
|
||||
|
||||
// Now we repeat the same process, but now the address has the expected subnetwork ID.
|
||||
|
||||
// Add a new address and get it
|
||||
err = n.AddAddressByIP(someIP+":8333", localSubnetworkID)
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
ka = n.GetAddress()
|
||||
if ka == nil {
|
||||
t.Fatalf("Did not get an address where there is one in the pool")
|
||||
}
|
||||
if ka.NetAddress().IP.String() != someIP {
|
||||
t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().IP.String(), someIP)
|
||||
}
|
||||
if !ka.SubnetworkID().IsEqual(localSubnetworkID) {
|
||||
t.Errorf("Wrong Subnetwork ID: got %v, want %v", *ka.SubnetworkID(), localSubnetworkID)
|
||||
}
|
||||
n.Attempt(ka.NetAddress())
|
||||
|
||||
// Mark this as a good address and get it
|
||||
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")
|
||||
}
|
||||
if ka.NetAddress().IP.String() != someIP {
|
||||
t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().IP.String(), someIP)
|
||||
}
|
||||
if *ka.SubnetworkID() != *localSubnetworkID {
|
||||
t.Errorf("Wrong Subnetwork ID: got %v, want %v", ka.SubnetworkID(), localSubnetworkID)
|
||||
}
|
||||
|
||||
numAddrs = n.TotalNumAddresses()
|
||||
if numAddrs != 2 {
|
||||
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBestLocalAddress(t *testing.T) {
|
||||
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")},
|
||||
{IP: net.ParseIP("2001:470::1")},
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
remoteAddr wire.NetAddress
|
||||
want0 wire.NetAddress
|
||||
want1 wire.NetAddress
|
||||
want2 wire.NetAddress
|
||||
want3 wire.NetAddress
|
||||
}{
|
||||
{
|
||||
// Remote connection from public IPv4
|
||||
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
|
||||
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
|
||||
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
|
||||
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 := New("testgetbestlocaladdress", nil, nil)
|
||||
|
||||
// Test against default when there's no address
|
||||
for x, test := range tests {
|
||||
got := amgr.GetBestLocalAddress(&test.remoteAddr)
|
||||
if !test.want0.IP.Equal(got.IP) {
|
||||
t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s",
|
||||
x, test.remoteAddr.IP, test.want1.IP, got.IP)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, localAddr := range localAddrs {
|
||||
amgr.AddLocalAddress(&localAddr, InterfacePrio)
|
||||
}
|
||||
|
||||
// Test against want1
|
||||
for x, test := range tests {
|
||||
got := amgr.GetBestLocalAddress(&test.remoteAddr)
|
||||
if !test.want1.IP.Equal(got.IP) {
|
||||
t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s",
|
||||
x, test.remoteAddr.IP, test.want1.IP, got.IP)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Add a public IP to the list of local addresses.
|
||||
localAddr := wire.NetAddress{IP: net.ParseIP("204.124.8.100")}
|
||||
amgr.AddLocalAddress(&localAddr, InterfacePrio)
|
||||
|
||||
// Test against want2
|
||||
for x, test := range tests {
|
||||
got := amgr.GetBestLocalAddress(&test.remoteAddr)
|
||||
if !test.want2.IP.Equal(got.IP) {
|
||||
t.Errorf("TestGetBestLocalAddress test2 #%d failed for remote address %s: want %s got %s",
|
||||
x, test.remoteAddr.IP, test.want2.IP, got.IP)
|
||||
continue
|
||||
}
|
||||
}
|
||||
/*
|
||||
// Add a Tor generated IP address
|
||||
localAddr = wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}
|
||||
amgr.AddLocalAddress(&localAddr, ManualPrio)
|
||||
// Test against want3
|
||||
for x, test := range tests {
|
||||
got := amgr.GetBestLocalAddress(&test.remoteAddr)
|
||||
if !test.want3.IP.Equal(got.IP) {
|
||||
t.Errorf("TestGetBestLocalAddress test3 #%d failed for remote address %s: want %s got %s",
|
||||
x, test.remoteAddr.IP, test.want3.IP, got.IP)
|
||||
continue
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func TestNetAddressKey(t *testing.T) {
|
||||
addNaTests()
|
||||
|
||||
|
||||
@@ -10,4 +10,4 @@ import (
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.ADXR)
|
||||
var spawn = panics.GoroutineWrapperFuncWithPanicHandler(log)
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
|
||||
222
addrmgr/network_test.go
Normal file
222
addrmgr/network_test.go
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addrmgr_test
|
||||
|
||||
import (
|
||||
"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) {
|
||||
originalActiveCfg := config.ActiveConfig()
|
||||
config.SetActiveConfig(&config.Config{
|
||||
Flags: &config.Flags{
|
||||
NetworkFlags: config.NetworkFlags{
|
||||
ActiveNetParams: &dagconfig.SimnetParams},
|
||||
},
|
||||
})
|
||||
defer config.SetActiveConfig(originalActiveCfg)
|
||||
|
||||
type ipTest struct {
|
||||
in wire.NetAddress
|
||||
rfc1918 bool
|
||||
rfc2544 bool
|
||||
rfc3849 bool
|
||||
rfc3927 bool
|
||||
rfc3964 bool
|
||||
rfc4193 bool
|
||||
rfc4380 bool
|
||||
rfc4843 bool
|
||||
rfc4862 bool
|
||||
rfc5737 bool
|
||||
rfc6052 bool
|
||||
rfc6145 bool
|
||||
rfc6598 bool
|
||||
local bool
|
||||
valid bool
|
||||
routable bool
|
||||
}
|
||||
|
||||
newIPTest := func(ip string, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964,
|
||||
rfc4193, rfc4380, rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598,
|
||||
local, valid, routable bool) ipTest {
|
||||
nip := net.ParseIP(ip)
|
||||
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
|
||||
}
|
||||
|
||||
tests := []ipTest{
|
||||
newIPTest("10.255.255.255", true, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false, true, false),
|
||||
newIPTest("192.168.0.1", true, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false, true, false),
|
||||
newIPTest("172.31.255.1", true, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false, true, false),
|
||||
newIPTest("172.32.1.1", false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, true, true),
|
||||
newIPTest("169.254.250.120", false, false, false, true, false, false,
|
||||
false, false, false, false, false, false, false, false, true, false),
|
||||
newIPTest("0.0.0.0", false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, true, false, false),
|
||||
newIPTest("255.255.255.255", false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false, false, false),
|
||||
newIPTest("127.0.0.1", false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, true, true, false),
|
||||
newIPTest("fd00:dead::1", false, false, false, false, false, true,
|
||||
false, false, false, false, false, false, false, false, true, false),
|
||||
newIPTest("2001::1", false, false, false, false, false, false,
|
||||
true, false, false, false, false, false, false, false, true, true),
|
||||
newIPTest("2001:10:abcd::1:1", false, false, false, false, false, false,
|
||||
false, true, false, false, false, false, false, false, true, false),
|
||||
newIPTest("fe80::1", false, false, false, false, false, false,
|
||||
false, false, true, false, false, false, false, false, true, false),
|
||||
newIPTest("fe80:1::1", false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false, true, true),
|
||||
newIPTest("64:ff9b::1", false, false, false, false, false, false,
|
||||
false, false, false, false, true, false, false, false, true, true),
|
||||
newIPTest("::ffff:abcd:ef12:1", false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false, true, true),
|
||||
newIPTest("::1", false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, true, true, false),
|
||||
newIPTest("198.18.0.1", false, true, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, true, false),
|
||||
newIPTest("100.127.255.1", false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, true, false, true, false),
|
||||
newIPTest("203.0.113.1", false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, true, false),
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for _, test := range tests {
|
||||
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 := addrmgr.IsRFC3849(&test.in); rv != test.rfc3849 {
|
||||
t.Errorf("IsRFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849)
|
||||
}
|
||||
|
||||
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 := addrmgr.IsRFC3964(&test.in); rv != test.rfc3964 {
|
||||
t.Errorf("IsRFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964)
|
||||
}
|
||||
|
||||
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 := addrmgr.IsRFC4380(&test.in); rv != test.rfc4380 {
|
||||
t.Errorf("IsRFC4380 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4380)
|
||||
}
|
||||
|
||||
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 := addrmgr.IsRFC4862(&test.in); rv != test.rfc4862 {
|
||||
t.Errorf("IsRFC4862 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4862)
|
||||
}
|
||||
|
||||
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 := addrmgr.IsRFC6145(&test.in); rv != test.rfc6145 {
|
||||
t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6145)
|
||||
}
|
||||
|
||||
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 := addrmgr.IsValid(&test.in); rv != test.valid {
|
||||
t.Errorf("IsValid %s\n got: %v want: %v", test.in.IP, rv, test.valid)
|
||||
}
|
||||
|
||||
if rv := addrmgr.IsRoutable(&test.in); rv != test.routable {
|
||||
t.Errorf("IsRoutable %s\n got: %v want: %v", test.in.IP, rv, test.routable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGroupKey tests the GroupKey function to ensure it properly groups various
|
||||
// IP addresses.
|
||||
func TestGroupKey(t *testing.T) {
|
||||
originalActiveCfg := config.ActiveConfig()
|
||||
config.SetActiveConfig(&config.Config{
|
||||
Flags: &config.Flags{
|
||||
NetworkFlags: config.NetworkFlags{
|
||||
ActiveNetParams: &dagconfig.SimnetParams},
|
||||
},
|
||||
})
|
||||
defer config.SetActiveConfig(originalActiveCfg)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
expected string
|
||||
}{
|
||||
// Local addresses.
|
||||
{name: "ipv4 localhost", ip: "127.0.0.1", expected: "local"},
|
||||
{name: "ipv6 localhost", ip: "::1", expected: "local"},
|
||||
{name: "ipv4 zero", ip: "0.0.0.0", expected: "local"},
|
||||
{name: "ipv4 first octet zero", ip: "0.1.2.3", expected: "local"},
|
||||
|
||||
// Unroutable addresses.
|
||||
{name: "ipv4 invalid bcast", ip: "255.255.255.255", expected: "unroutable"},
|
||||
{name: "ipv4 rfc1918 10/8", ip: "10.1.2.3", expected: "unroutable"},
|
||||
{name: "ipv4 rfc1918 172.16/12", ip: "172.16.1.2", expected: "unroutable"},
|
||||
{name: "ipv4 rfc1918 192.168/16", ip: "192.168.1.2", expected: "unroutable"},
|
||||
{name: "ipv6 rfc3849 2001:db8::/32", ip: "2001:db8::1234", expected: "unroutable"},
|
||||
{name: "ipv4 rfc3927 169.254/16", ip: "169.254.1.2", expected: "unroutable"},
|
||||
{name: "ipv6 rfc4193 fc00::/7", ip: "fc00::1234", expected: "unroutable"},
|
||||
{name: "ipv6 rfc4843 2001:10::/28", ip: "2001:10::1234", expected: "unroutable"},
|
||||
{name: "ipv6 rfc4862 fe80::/64", ip: "fe80::1234", expected: "unroutable"},
|
||||
|
||||
// IPv4 normal.
|
||||
{name: "ipv4 normal class a", ip: "12.1.2.3", expected: "12.1.0.0"},
|
||||
{name: "ipv4 normal class b", ip: "173.1.2.3", expected: "173.1.0.0"},
|
||||
{name: "ipv4 normal class c", ip: "196.1.2.3", expected: "196.1.0.0"},
|
||||
|
||||
// IPv6/IPv4 translations.
|
||||
{name: "ipv6 rfc3964 with ipv4 encap", ip: "2002:0c01:0203::", expected: "12.1.0.0"},
|
||||
{name: "ipv6 rfc4380 toredo ipv4", ip: "2001:0:1234::f3fe:fdfc", expected: "12.1.0.0"},
|
||||
{name: "ipv6 rfc6052 well-known prefix with ipv4", ip: "64:ff9b::0c01:0203", expected: "12.1.0.0"},
|
||||
{name: "ipv6 rfc6145 translated ipv4", ip: "::ffff:0:0c01:0203", expected: "12.1.0.0"},
|
||||
|
||||
// Tor.
|
||||
{name: "ipv6 tor onioncat", ip: "fd87:d87e:eb43:1234::5678", expected: "unroutable"},
|
||||
{name: "ipv6 tor onioncat 2", ip: "fd87:d87e:eb43:1245::6789", expected: "unroutable"},
|
||||
{name: "ipv6 tor onioncat 3", ip: "fd87:d87e:eb43:1345::6789", expected: "unroutable"},
|
||||
|
||||
// IPv6 normal.
|
||||
{name: "ipv6 normal", ip: "2602:100::1", expected: "2602:100::"},
|
||||
{name: "ipv6 normal 2", ip: "2602:0100::1234", expected: "2602:100::"},
|
||||
{name: "ipv6 hurricane electric", ip: "2001:470:1f10:a1::2", expected: "2001:470:1000::"},
|
||||
{name: "ipv6 hurricane electric 2", ip: "2001:0470:1f10:a1::2", expected: "2001:470:1000::"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
nip := net.ParseIP(test.ip)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
blockchain
|
||||
==========
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad/blockchain)
|
||||
|
||||
Package blockdag implements Kaspa block handling, organization of the blockDAG,
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"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, newSet())
|
||||
newNode, _ := dag.newBlockNode(blockHeader, newBlockSet())
|
||||
newNode.status = statusInvalidAncestor
|
||||
dag.index.AddNode(newNode)
|
||||
return dag.index.flushToDB()
|
||||
@@ -30,7 +31,8 @@ func (dag *BlockDAG) addNodeToIndexWithInvalidAncestor(block *util.Block) error
|
||||
func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) error {
|
||||
parents, err := lookupParentNodes(block, dag)
|
||||
if err != nil {
|
||||
if rErr, ok := err.(RuleError); ok && rErr.ErrorCode == ErrInvalidAncestorBlock {
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrInvalidAncestorBlock {
|
||||
err := dag.addNodeToIndexWithInvalidAncestor(block)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -112,14 +114,14 @@ func lookupParentNodes(block *util.Block, blockDAG *BlockDAG) (blockSet, error)
|
||||
header := block.MsgBlock().Header
|
||||
parentHashes := header.ParentHashes
|
||||
|
||||
nodes := newSet()
|
||||
nodes := newBlockSet()
|
||||
for _, parentHash := range parentHashes {
|
||||
node := blockDAG.index.LookupNode(parentHash)
|
||||
if node == nil {
|
||||
str := fmt.Sprintf("parent block %s is unknown", parentHashes)
|
||||
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", parentHashes)
|
||||
str := fmt.Sprintf("parent block %s is known to be invalid", parentHash)
|
||||
return nil, ruleError(ErrInvalidAncestorBlock, str)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -33,8 +34,8 @@ func TestMaybeAcceptBlockErrors(t *testing.T) {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||
"Expected: %s, got: <nil>", ErrParentBlockUnknown)
|
||||
}
|
||||
ruleErr, ok := err.(RuleError)
|
||||
if !ok {
|
||||
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 {
|
||||
@@ -71,8 +72,7 @@ func TestMaybeAcceptBlockErrors(t *testing.T) {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||
"Expected: %s, got: <nil>", ErrInvalidAncestorBlock)
|
||||
}
|
||||
ruleErr, ok = err.(RuleError)
|
||||
if !ok {
|
||||
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 {
|
||||
@@ -91,8 +91,7 @@ func TestMaybeAcceptBlockErrors(t *testing.T) {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||
"Expected: %s, got: <nil>", ErrUnexpectedDifficulty)
|
||||
}
|
||||
ruleErr, ok = err.(RuleError)
|
||||
if !ok {
|
||||
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 {
|
||||
|
||||
@@ -19,12 +19,12 @@ func TestBlockHeap(t *testing.T) {
|
||||
defer teardownFunc()
|
||||
|
||||
block0Header := dagconfig.MainnetParams.GenesisBlock.Header
|
||||
block0, _ := dag.newBlockNode(&block0Header, newSet())
|
||||
block0, _ := dag.newBlockNode(&block0Header, newBlockSet())
|
||||
|
||||
block100000Header := Block100000.Header
|
||||
block100000, _ := dag.newBlockNode(&block100000Header, setFromSlice(block0))
|
||||
block100000, _ := dag.newBlockNode(&block100000Header, blockSetFromSlice(block0))
|
||||
|
||||
block0smallHash, _ := dag.newBlockNode(&block0Header, newSet())
|
||||
block0smallHash, _ := dag.newBlockNode(&block0Header, newBlockSet())
|
||||
block0smallHash.hash = &daghash.Hash{}
|
||||
|
||||
tests := []struct {
|
||||
|
||||
@@ -43,8 +43,8 @@ func newBlockIndex(db database.DB, dagParams *dagconfig.Params) *blockIndex {
|
||||
// This function is safe for concurrent access.
|
||||
func (bi *blockIndex) HaveBlock(hash *daghash.Hash) bool {
|
||||
bi.RLock()
|
||||
defer bi.RUnlock()
|
||||
_, hasBlock := bi.index[*hash]
|
||||
bi.RUnlock()
|
||||
return hasBlock
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ func (bi *blockIndex) HaveBlock(hash *daghash.Hash) bool {
|
||||
// This function is safe for concurrent access.
|
||||
func (bi *blockIndex) LookupNode(hash *daghash.Hash) *blockNode {
|
||||
bi.RLock()
|
||||
defer bi.RUnlock()
|
||||
node := bi.index[*hash]
|
||||
bi.RUnlock()
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -65,9 +65,9 @@ func (bi *blockIndex) LookupNode(hash *daghash.Hash) *blockNode {
|
||||
// This function is safe for concurrent access.
|
||||
func (bi *blockIndex) AddNode(node *blockNode) {
|
||||
bi.Lock()
|
||||
defer bi.Unlock()
|
||||
bi.addNode(node)
|
||||
bi.dirty[node] = struct{}{}
|
||||
bi.Unlock()
|
||||
}
|
||||
|
||||
// addNode adds the provided node to the block index, but does not mark it as
|
||||
@@ -83,8 +83,8 @@ func (bi *blockIndex) addNode(node *blockNode) {
|
||||
// This function is safe for concurrent access.
|
||||
func (bi *blockIndex) NodeStatus(node *blockNode) blockStatus {
|
||||
bi.RLock()
|
||||
defer bi.RUnlock()
|
||||
status := node.status
|
||||
bi.RUnlock()
|
||||
return status
|
||||
}
|
||||
|
||||
@@ -95,9 +95,9 @@ func (bi *blockIndex) NodeStatus(node *blockNode) blockStatus {
|
||||
// This function is safe for concurrent access.
|
||||
func (bi *blockIndex) SetStatusFlags(node *blockNode, flags blockStatus) {
|
||||
bi.Lock()
|
||||
defer bi.Unlock()
|
||||
node.status |= flags
|
||||
bi.dirty[node] = struct{}{}
|
||||
bi.Unlock()
|
||||
}
|
||||
|
||||
// UnsetStatusFlags flips the provided status flags on the block node to off,
|
||||
@@ -106,9 +106,9 @@ func (bi *blockIndex) SetStatusFlags(node *blockNode, flags blockStatus) {
|
||||
// This function is safe for concurrent access.
|
||||
func (bi *blockIndex) UnsetStatusFlags(node *blockNode, flags blockStatus) {
|
||||
bi.Lock()
|
||||
defer bi.Unlock()
|
||||
node.status &^= flags
|
||||
bi.dirty[node] = struct{}{}
|
||||
bi.Unlock()
|
||||
}
|
||||
|
||||
// flushToDB writes all dirty block nodes to the database. If all writes
|
||||
@@ -134,8 +134,9 @@ func (bi *blockIndex) flushToDBWithTx(dbTx database.Tx) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If write was successful, clear the dirty set.
|
||||
bi.dirty = make(map[*blockNode]struct{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bi *blockIndex) clearDirtyEntries() {
|
||||
bi.dirty = make(map[*blockNode]struct{})
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestAncestorErrors(t *testing.T) {
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
node := newTestNode(dag, newSet(), int32(0x10000000), 0, time.Unix(0, 0))
|
||||
node := newTestNode(dag, newBlockSet(), int32(0x10000000), 0, time.Unix(0, 0))
|
||||
node.blueScore = 2
|
||||
ancestor := node.SelectedAncestor(3)
|
||||
if ancestor != nil {
|
||||
|
||||
@@ -31,11 +31,6 @@ const (
|
||||
// statusInvalidAncestor indicates that one of the block's ancestors has
|
||||
// has failed validation, thus the block is also invalid.
|
||||
statusInvalidAncestor
|
||||
|
||||
// statusNone indicates that the block has no validation state flags set.
|
||||
//
|
||||
// NOTE: This must be defined last in order to avoid influencing iota.
|
||||
statusNone blockStatus = 0
|
||||
)
|
||||
|
||||
// KnownValid returns whether the block is known to be valid. This will return
|
||||
@@ -80,7 +75,7 @@ type blockNode struct {
|
||||
|
||||
// bluesAnticoneSizes is a map holding the set of blues affected by this block and their
|
||||
// modified blue anticone size.
|
||||
bluesAnticoneSizes map[daghash.Hash]dagconfig.KType
|
||||
bluesAnticoneSizes map[*blockNode]dagconfig.KType
|
||||
|
||||
// hash is the double sha 256 of the block.
|
||||
hash *daghash.Hash
|
||||
@@ -116,7 +111,7 @@ func (dag *BlockDAG) newBlockNode(blockHeader *wire.BlockHeader, parents blockSe
|
||||
children: make(blockSet),
|
||||
blueScore: math.MaxUint64, // Initialized to the max value to avoid collisions with the genesis block
|
||||
timestamp: dag.AdjustedTime().Unix(),
|
||||
bluesAnticoneSizes: make(map[daghash.Hash]dagconfig.KType),
|
||||
bluesAnticoneSizes: make(map[*blockNode]dagconfig.KType),
|
||||
}
|
||||
|
||||
// blockHeader is nil only for the virtual block
|
||||
@@ -184,7 +179,7 @@ func (node *blockNode) Header() *wire.BlockHeader {
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (node *blockNode) SelectedAncestor(blueScore uint64) *blockNode {
|
||||
if blueScore < 0 || blueScore > node.blueScore {
|
||||
if blueScore > node.blueScore {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -25,16 +25,17 @@ func TestBlueAnticoneSizesSize(t *testing.T) {
|
||||
}
|
||||
|
||||
blockHeader := dagconfig.SimnetParams.GenesisBlock.Header
|
||||
node, _ := dag.newBlockNode(&blockHeader, newSet())
|
||||
hash := daghash.Hash{1}
|
||||
node, _ := dag.newBlockNode(&blockHeader, newBlockSet())
|
||||
fakeBlue := &blockNode{hash: &daghash.Hash{1}}
|
||||
dag.index.AddNode(fakeBlue)
|
||||
// Setting maxKType to maximum value of KType.
|
||||
// As we verify above that KType is unsigned we can be sure that maxKType is indeed the maximum value of KType.
|
||||
maxKType := ^dagconfig.KType(0)
|
||||
node.bluesAnticoneSizes[hash] = maxKType
|
||||
node.bluesAnticoneSizes[fakeBlue] = maxKType
|
||||
serializedNode, _ := serializeBlockNode(node)
|
||||
deserializedNode, _ := dag.deserializeBlockNode(serializedNode)
|
||||
if deserializedNode.bluesAnticoneSizes[hash] != maxKType {
|
||||
if deserializedNode.bluesAnticoneSizes[fakeBlue] != maxKType {
|
||||
t.Fatalf("TestBlueAnticoneSizesSize: BlueAnticoneSize should not change when deserializing. Expected: %v but got %v",
|
||||
maxKType, deserializedNode.bluesAnticoneSizes[hash])
|
||||
maxKType, deserializedNode.bluesAnticoneSizes[fakeBlue])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ import (
|
||||
// blockSet implements a basic unsorted set of blocks
|
||||
type blockSet map[*blockNode]struct{}
|
||||
|
||||
// newSet creates a new, empty BlockSet
|
||||
func newSet() blockSet {
|
||||
// newBlockSet creates a new, empty BlockSet
|
||||
func newBlockSet() blockSet {
|
||||
return map[*blockNode]struct{}{}
|
||||
}
|
||||
|
||||
// setFromSlice converts a slice of blockNodes into an unordered set represented as map
|
||||
func setFromSlice(nodes ...*blockNode) blockSet {
|
||||
set := newSet()
|
||||
// blockSetFromSlice converts a slice of blockNodes into an unordered set represented as map
|
||||
func blockSetFromSlice(nodes ...*blockNode) blockSet {
|
||||
set := newBlockSet()
|
||||
for _, node := range nodes {
|
||||
set.add(node)
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func (bs blockSet) remove(node *blockNode) {
|
||||
|
||||
// clone clones thie block set
|
||||
func (bs blockSet) clone() blockSet {
|
||||
clone := newSet()
|
||||
clone := newBlockSet()
|
||||
for node := range bs {
|
||||
clone.add(node)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func (bs blockSet) clone() blockSet {
|
||||
|
||||
// subtract returns the difference between the BlockSet and another BlockSet
|
||||
func (bs blockSet) subtract(other blockSet) blockSet {
|
||||
diff := newSet()
|
||||
diff := newBlockSet()
|
||||
for node := range bs {
|
||||
if !other.contains(node) {
|
||||
diff.add(node)
|
||||
@@ -102,17 +102,6 @@ func (bs blockSet) String() string {
|
||||
return strings.Join(nodeStrs, ",")
|
||||
}
|
||||
|
||||
// anyChildInSet returns true iff any child of node is contained within this set
|
||||
func (bs blockSet) anyChildInSet(node *blockNode) bool {
|
||||
for child := range node.children {
|
||||
if bs.contains(child) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (bs blockSet) bluest() *blockNode {
|
||||
var bluestNode *blockNode
|
||||
var maxScore uint64
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestHashes(t *testing.T) {
|
||||
bs := setFromSlice(
|
||||
bs := blockSetFromSlice(
|
||||
&blockNode{
|
||||
hash: &daghash.Hash{3},
|
||||
},
|
||||
@@ -49,33 +49,33 @@ func TestBlockSetSubtract(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "both sets empty",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(),
|
||||
setA: blockSetFromSlice(),
|
||||
setB: blockSetFromSlice(),
|
||||
expectedResult: blockSetFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "subtract an empty set",
|
||||
setA: setFromSlice(node1),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(node1),
|
||||
setA: blockSetFromSlice(node1),
|
||||
setB: blockSetFromSlice(),
|
||||
expectedResult: blockSetFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "subtract from empty set",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(),
|
||||
setA: blockSetFromSlice(),
|
||||
setB: blockSetFromSlice(node1),
|
||||
expectedResult: blockSetFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "subtract unrelated set",
|
||||
setA: setFromSlice(node1),
|
||||
setB: setFromSlice(node2),
|
||||
expectedResult: setFromSlice(node1),
|
||||
setA: blockSetFromSlice(node1),
|
||||
setB: blockSetFromSlice(node2),
|
||||
expectedResult: blockSetFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "typical case",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node2, node3),
|
||||
expectedResult: setFromSlice(node1),
|
||||
setA: blockSetFromSlice(node1, node2),
|
||||
setB: blockSetFromSlice(node2, node3),
|
||||
expectedResult: blockSetFromSlice(node1),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -101,33 +101,33 @@ func TestBlockSetAddSet(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "both sets empty",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(),
|
||||
setA: blockSetFromSlice(),
|
||||
setB: blockSetFromSlice(),
|
||||
expectedResult: blockSetFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "add an empty set",
|
||||
setA: setFromSlice(node1),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(node1),
|
||||
setA: blockSetFromSlice(node1),
|
||||
setB: blockSetFromSlice(),
|
||||
expectedResult: blockSetFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "add to empty set",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(node1),
|
||||
setA: blockSetFromSlice(),
|
||||
setB: blockSetFromSlice(node1),
|
||||
expectedResult: blockSetFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "add already added member",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(node1, node2),
|
||||
setA: blockSetFromSlice(node1, node2),
|
||||
setB: blockSetFromSlice(node1),
|
||||
expectedResult: blockSetFromSlice(node1, node2),
|
||||
},
|
||||
{
|
||||
name: "typical case",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node2, node3),
|
||||
expectedResult: setFromSlice(node1, node2, node3),
|
||||
setA: blockSetFromSlice(node1, node2),
|
||||
setB: blockSetFromSlice(node2, node3),
|
||||
expectedResult: blockSetFromSlice(node1, node2, node3),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -153,33 +153,33 @@ func TestBlockSetAddSlice(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "add empty slice to empty set",
|
||||
set: setFromSlice(),
|
||||
set: blockSetFromSlice(),
|
||||
slice: []*blockNode{},
|
||||
expectedResult: setFromSlice(),
|
||||
expectedResult: blockSetFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "add an empty slice",
|
||||
set: setFromSlice(node1),
|
||||
set: blockSetFromSlice(node1),
|
||||
slice: []*blockNode{},
|
||||
expectedResult: setFromSlice(node1),
|
||||
expectedResult: blockSetFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "add to empty set",
|
||||
set: setFromSlice(),
|
||||
set: blockSetFromSlice(),
|
||||
slice: []*blockNode{node1},
|
||||
expectedResult: setFromSlice(node1),
|
||||
expectedResult: blockSetFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "add already added member",
|
||||
set: setFromSlice(node1, node2),
|
||||
set: blockSetFromSlice(node1, node2),
|
||||
slice: []*blockNode{node1},
|
||||
expectedResult: setFromSlice(node1, node2),
|
||||
expectedResult: blockSetFromSlice(node1, node2),
|
||||
},
|
||||
{
|
||||
name: "typical case",
|
||||
set: setFromSlice(node1, node2),
|
||||
set: blockSetFromSlice(node1, node2),
|
||||
slice: []*blockNode{node2, node3},
|
||||
expectedResult: setFromSlice(node1, node2, node3),
|
||||
expectedResult: blockSetFromSlice(node1, node2, node3),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -205,33 +205,33 @@ func TestBlockSetUnion(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "both sets empty",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(),
|
||||
setA: blockSetFromSlice(),
|
||||
setB: blockSetFromSlice(),
|
||||
expectedResult: blockSetFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "union against an empty set",
|
||||
setA: setFromSlice(node1),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(node1),
|
||||
setA: blockSetFromSlice(node1),
|
||||
setB: blockSetFromSlice(),
|
||||
expectedResult: blockSetFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "union from an empty set",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(node1),
|
||||
setA: blockSetFromSlice(),
|
||||
setB: blockSetFromSlice(node1),
|
||||
expectedResult: blockSetFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "union with subset",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(node1, node2),
|
||||
setA: blockSetFromSlice(node1, node2),
|
||||
setB: blockSetFromSlice(node1),
|
||||
expectedResult: blockSetFromSlice(node1, node2),
|
||||
},
|
||||
{
|
||||
name: "typical case",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node2, node3),
|
||||
expectedResult: setFromSlice(node1, node2, node3),
|
||||
setA: blockSetFromSlice(node1, node2),
|
||||
setB: blockSetFromSlice(node2, node3),
|
||||
expectedResult: blockSetFromSlice(node1, node2, node3),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -23,17 +23,6 @@ import (
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
func loadBlocksWithLog(t *testing.T, filename string) ([]*util.Block, error) {
|
||||
blocks, err := LoadBlocks(filename)
|
||||
if err == nil {
|
||||
t.Logf("Loaded %d blocks from file %s", len(blocks), filename)
|
||||
for i, b := range blocks {
|
||||
t.Logf("Block #%d: %s", i, b.Hash())
|
||||
}
|
||||
}
|
||||
return blocks, err
|
||||
}
|
||||
|
||||
// loadUTXOSet returns a utxo view loaded from a file.
|
||||
func loadUTXOSet(filename string) (UTXOSet, error) {
|
||||
// The utxostore file format is:
|
||||
@@ -129,10 +118,10 @@ func newTestDAG(params *dagconfig.Params) *BlockDAG {
|
||||
|
||||
// Create a genesis block node and block index index populated with it
|
||||
// on the above fake DAG.
|
||||
dag.genesis, _ = dag.newBlockNode(¶ms.GenesisBlock.Header, newSet())
|
||||
dag.genesis, _ = dag.newBlockNode(¶ms.GenesisBlock.Header, newBlockSet())
|
||||
index.AddNode(dag.genesis)
|
||||
|
||||
dag.virtual = newVirtualBlock(dag, setFromSlice(dag.genesis))
|
||||
dag.virtual = newVirtualBlock(dag, blockSetFromSlice(dag.genesis))
|
||||
return dag
|
||||
}
|
||||
|
||||
|
||||
102
blockdag/dag.go
102
blockdag/dag.go
@@ -155,23 +155,23 @@ type BlockDAG struct {
|
||||
reachabilityStore *reachabilityStore
|
||||
}
|
||||
|
||||
// HaveBlock returns whether or not the DAG instance has the block represented
|
||||
// IsKnownBlock returns whether or not the DAG instance has the block represented
|
||||
// by the passed hash. This includes checking the various places a block can
|
||||
// be in, like part of the DAG or the orphan pool.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) HaveBlock(hash *daghash.Hash) bool {
|
||||
return dag.BlockExists(hash) || dag.IsKnownOrphan(hash) || dag.isKnownDelayedBlock(hash)
|
||||
func (dag *BlockDAG) IsKnownBlock(hash *daghash.Hash) bool {
|
||||
return dag.IsInDAG(hash) || dag.IsKnownOrphan(hash) || dag.isKnownDelayedBlock(hash)
|
||||
}
|
||||
|
||||
// HaveBlocks returns whether or not the DAG instances has all blocks represented
|
||||
// AreKnownBlocks returns whether or not the DAG instances has all blocks represented
|
||||
// by the passed hashes. This includes checking the various places a block can
|
||||
// be in, like part of the DAG or the orphan pool.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) HaveBlocks(hashes []*daghash.Hash) bool {
|
||||
func (dag *BlockDAG) AreKnownBlocks(hashes []*daghash.Hash) bool {
|
||||
for _, hash := range hashes {
|
||||
haveBlock := dag.HaveBlock(hash)
|
||||
haveBlock := dag.IsKnownBlock(hash)
|
||||
if !haveBlock {
|
||||
return false
|
||||
}
|
||||
@@ -194,8 +194,8 @@ func (dag *BlockDAG) IsKnownOrphan(hash *daghash.Hash) bool {
|
||||
// Protect concurrent access. Using a read lock only so multiple
|
||||
// readers can query without blocking each other.
|
||||
dag.orphanLock.RLock()
|
||||
defer dag.orphanLock.RUnlock()
|
||||
_, exists := dag.orphans[*hash]
|
||||
dag.orphanLock.RUnlock()
|
||||
|
||||
return exists
|
||||
}
|
||||
@@ -232,11 +232,9 @@ func (dag *BlockDAG) GetOrphanMissingAncestorHashes(orphanHash *daghash.Hash) ([
|
||||
visited[*current] = true
|
||||
orphan, orphanExists := dag.orphans[*current]
|
||||
if orphanExists {
|
||||
for _, parentHash := range orphan.block.MsgBlock().Header.ParentHashes {
|
||||
queue = append(queue, parentHash)
|
||||
}
|
||||
queue = append(queue, orphan.block.MsgBlock().Header.ParentHashes...)
|
||||
} else {
|
||||
if !dag.BlockExists(current) && current != orphanHash {
|
||||
if !dag.IsInDAG(current) && current != orphanHash {
|
||||
missingAncestorsHashes = append(missingAncestorsHashes, current)
|
||||
}
|
||||
}
|
||||
@@ -486,7 +484,7 @@ func (dag *BlockDAG) addBlock(node *blockNode,
|
||||
// Connect the block to the DAG.
|
||||
chainUpdates, err := dag.connectBlock(node, block, selectedParentAnticone, fastAdd)
|
||||
if err != nil {
|
||||
if _, ok := err.(RuleError); ok {
|
||||
if errors.As(err, &RuleError{}) {
|
||||
dag.index.SetStatusFlags(node, statusValidateFailed)
|
||||
} else {
|
||||
return nil, err
|
||||
@@ -576,8 +574,9 @@ func (dag *BlockDAG) connectBlock(node *blockNode,
|
||||
newBlockUTXO, txsAcceptanceData, newBlockFeeData, err := node.verifyAndBuildUTXO(dag, block.Transactions(), fastAdd)
|
||||
if err != nil {
|
||||
newErrString := fmt.Sprintf("error verifying UTXO for %s: %s", node, err)
|
||||
if err, ok := err.(RuleError); ok {
|
||||
return nil, ruleError(err.ErrorCode, newErrString)
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); ok {
|
||||
return nil, ruleError(ruleErr.ErrorCode, newErrString)
|
||||
}
|
||||
return nil, errors.New(newErrString)
|
||||
}
|
||||
@@ -673,6 +672,7 @@ func (dag *BlockDAG) saveChangesFromBlock(block *util.Block, virtualUTXODiff *UT
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dag.index.clearDirtyEntries()
|
||||
dag.utxoDiffStore.clearDirtyEntries()
|
||||
dag.reachabilityStore.clearDirtyEntries()
|
||||
return nil
|
||||
@@ -835,16 +835,6 @@ func (dag *BlockDAG) NextBlockCoinbaseTransactionNoLock(scriptPubKey []byte, ext
|
||||
return dag.virtual.blockNode.expectedCoinbaseTransaction(dag, txsAcceptanceData, scriptPubKey, extraData)
|
||||
}
|
||||
|
||||
// NextAcceptedIDMerkleRoot prepares the acceptedIDMerkleRoot for the next mined block
|
||||
//
|
||||
// This function CAN'T be called with the DAG lock held.
|
||||
func (dag *BlockDAG) NextAcceptedIDMerkleRoot() (*daghash.Hash, error) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
|
||||
return dag.NextAcceptedIDMerkleRootNoLock()
|
||||
}
|
||||
|
||||
// NextAcceptedIDMerkleRootNoLock prepares the acceptedIDMerkleRoot for the next mined block
|
||||
//
|
||||
// This function MUST be called with the DAG read-lock held
|
||||
@@ -877,18 +867,6 @@ func (dag *BlockDAG) TxsAcceptedByBlockHash(blockHash *daghash.Hash) (MultiBlock
|
||||
return txsAcceptanceData, err
|
||||
}
|
||||
|
||||
// BlockPastUTXO retrieves the past UTXO of this block
|
||||
//
|
||||
// This function MUST be called with the DAG read-lock held
|
||||
func (dag *BlockDAG) BlockPastUTXO(blockHash *daghash.Hash) (UTXOSet, error) {
|
||||
node := dag.index.LookupNode(blockHash)
|
||||
if node == nil {
|
||||
return nil, errors.Errorf("Couldn't find block %s", blockHash)
|
||||
}
|
||||
pastUTXO, _, err := dag.pastUTXO(node)
|
||||
return pastUTXO, err
|
||||
}
|
||||
|
||||
// applyDAGChanges does the following:
|
||||
// 1. Connects each of the new block's parents to the block.
|
||||
// 2. Adds the new block to the DAG's tips.
|
||||
@@ -1211,6 +1189,8 @@ func (node *blockNode) acceptSelectedParentTransactions(selectedParent *util.Blo
|
||||
func (dag *BlockDAG) restoreUTXO(node *blockNode) (UTXOSet, error) {
|
||||
stack := []*blockNode{}
|
||||
|
||||
// Iterate over the chain of diff-childs from node till virtual and add them
|
||||
// all into a stack
|
||||
for current := node; current != nil; {
|
||||
stack = append(stack, current)
|
||||
var err error
|
||||
@@ -1220,20 +1200,28 @@ func (dag *BlockDAG) restoreUTXO(node *blockNode) (UTXOSet, error) {
|
||||
}
|
||||
}
|
||||
|
||||
utxo := UTXOSet(dag.virtual.utxoSet)
|
||||
// Start with the top item in the stack, going over it top-to-bottom,
|
||||
// applying the UTXO-diff one-by-one.
|
||||
topNode, stack := stack[len(stack)-1], stack[:len(stack)-1] // pop the top item in the stack
|
||||
topNodeDiff, err := dag.utxoDiffStore.diffByNode(topNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accumulatedDiff := topNodeDiff.clone()
|
||||
|
||||
for i := len(stack) - 1; i >= 0; i-- {
|
||||
diff, err := dag.utxoDiffStore.diffByNode(stack[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
utxo, err = utxo.WithDiff(diff)
|
||||
// Use WithDiffInPlace, otherwise copying the diffs again and again create a polynomial overhead
|
||||
err = accumulatedDiff.WithDiffInPlace(diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return utxo, nil
|
||||
return NewDiffUTXOSet(dag.virtual.utxoSet, accumulatedDiff), nil
|
||||
}
|
||||
|
||||
// updateTipsUTXO builds and applies new diff UTXOs for all the DAG's tips
|
||||
@@ -1388,6 +1376,23 @@ func (dag *BlockDAG) BlockConfirmationsByHashNoLock(hash *daghash.Hash) (uint64,
|
||||
return dag.blockConfirmations(node)
|
||||
}
|
||||
|
||||
// UTXOConfirmations returns the confirmations for the given outpoint, if it exists
|
||||
// in the DAG's UTXO set.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) UTXOConfirmations(outpoint *wire.Outpoint) (uint64, bool) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
|
||||
utxoEntry, ok := dag.GetUTXOEntry(*outpoint)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
confirmations := dag.SelectedTipBlueScore() - utxoEntry.BlockBlueScore() + 1
|
||||
|
||||
return confirmations, true
|
||||
}
|
||||
|
||||
// UTXOCommitment returns a commitment to the dag's current UTXOSet
|
||||
func (dag *BlockDAG) UTXOCommitment() string {
|
||||
return dag.UTXOSet().UTXOMultiset.Hash().String()
|
||||
@@ -1503,7 +1508,7 @@ func (dag *BlockDAG) SelectedParentChain(blockHash *daghash.Hash) ([]*daghash.Ha
|
||||
if blockHash == nil {
|
||||
blockHash = dag.genesis.hash
|
||||
}
|
||||
if !dag.BlockExists(blockHash) {
|
||||
if !dag.IsInDAG(blockHash) {
|
||||
return nil, nil, errors.Errorf("blockHash %s does not exist in the DAG", blockHash)
|
||||
}
|
||||
|
||||
@@ -1672,7 +1677,7 @@ func (dag *BlockDAG) antiPastBetween(lowHash, highHash *daghash.Hash, maxEntries
|
||||
// Collect every node in highNode's past (including itself) but
|
||||
// NOT in the lowNode's past (excluding itself) into an up-heap
|
||||
// (a heap sorted by blueScore from lowest to greatest).
|
||||
visited := newSet()
|
||||
visited := newBlockSet()
|
||||
candidateNodes := newUpHeap()
|
||||
queue := newDownHeap()
|
||||
queue.Push(highNode)
|
||||
@@ -1741,7 +1746,7 @@ func (dag *BlockDAG) antiPastHeadersBetween(lowHash, highHash *daghash.Hash, max
|
||||
}
|
||||
|
||||
// GetTopHeaders returns the top wire.MaxBlockHeadersPerMsg block headers ordered by blue score.
|
||||
func (dag *BlockDAG) GetTopHeaders(highHash *daghash.Hash) ([]*wire.BlockHeader, error) {
|
||||
func (dag *BlockDAG) GetTopHeaders(highHash *daghash.Hash, maxHeaders uint64) ([]*wire.BlockHeader, error) {
|
||||
highNode := &dag.virtual.blockNode
|
||||
if highHash != nil {
|
||||
highNode = dag.index.LookupNode(highHash)
|
||||
@@ -1753,10 +1758,9 @@ func (dag *BlockDAG) GetTopHeaders(highHash *daghash.Hash) ([]*wire.BlockHeader,
|
||||
queue := newDownHeap()
|
||||
queue.pushSet(highNode.parents)
|
||||
|
||||
visited := newSet()
|
||||
for i := uint32(0); queue.Len() > 0 && len(headers) < wire.MaxBlockHeadersPerMsg; i++ {
|
||||
var current *blockNode
|
||||
current = queue.pop()
|
||||
visited := newBlockSet()
|
||||
for i := uint32(0); queue.Len() > 0 && uint64(len(headers)) < maxHeaders; i++ {
|
||||
current := queue.pop()
|
||||
if !visited.contains(current) {
|
||||
visited.add(current)
|
||||
headers = append(headers, current.Header())
|
||||
@@ -1791,10 +1795,10 @@ func (dag *BlockDAG) RUnlock() {
|
||||
// wire.MaxBlockHeadersPerMsg block headers.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) AntiPastHeadersBetween(lowHash, highHash *daghash.Hash) ([]*wire.BlockHeader, error) {
|
||||
func (dag *BlockDAG) AntiPastHeadersBetween(lowHash, highHash *daghash.Hash, maxHeaders uint64) ([]*wire.BlockHeader, error) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
headers, err := dag.antiPastHeadersBetween(lowHash, highHash, wire.MaxBlockHeadersPerMsg)
|
||||
headers, err := dag.antiPastHeadersBetween(lowHash, highHash, maxHeaders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1834,7 +1838,7 @@ func (dag *BlockDAG) processDelayedBlocks() error {
|
||||
log.Errorf("Error while processing delayed block (block %s)", delayedBlock.block.Hash().String())
|
||||
// Rule errors should not be propagated as they refer only to the delayed block,
|
||||
// while this function runs in the context of another block
|
||||
if _, ok := err.(RuleError); !ok {
|
||||
if !errors.As(err, &RuleError{}) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -72,8 +73,8 @@ func TestBlockCount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestHaveBlock tests the HaveBlock API to ensure proper functionality.
|
||||
func TestHaveBlock(t *testing.T) {
|
||||
// TestIsKnownBlock tests the IsKnownBlock API to ensure proper functionality.
|
||||
func TestIsKnownBlock(t *testing.T) {
|
||||
// Load up blocks such that there is a fork in the DAG.
|
||||
// (genesis block) -> 1 -> 2 -> 3 -> 4
|
||||
// \-> 3b
|
||||
@@ -164,12 +165,13 @@ func TestHaveBlock(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatalf("ProcessBlock for block 3D has no error when expected to have an error\n")
|
||||
}
|
||||
rErr, ok := err.(RuleError)
|
||||
var ruleErr RuleError
|
||||
ok := errors.As(err, &ruleErr)
|
||||
if !ok {
|
||||
t.Fatalf("ProcessBlock for block 3D expected a RuleError, but got %v\n", err)
|
||||
}
|
||||
if !ok || rErr.ErrorCode != ErrDuplicateTxInputs {
|
||||
t.Fatalf("ProcessBlock for block 3D expected error code %s but got %s\n", ErrDuplicateTxInputs, rErr.ErrorCode)
|
||||
if !ok || ruleErr.ErrorCode != ErrDuplicateTxInputs {
|
||||
t.Fatalf("ProcessBlock for block 3D expected error code %s but got %s\n", ErrDuplicateTxInputs, ruleErr.ErrorCode)
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("ProcessBlock: block 3D " +
|
||||
@@ -217,9 +219,9 @@ func TestHaveBlock(t *testing.T) {
|
||||
t.Fatalf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
result := dag.HaveBlock(hash)
|
||||
result := dag.IsKnownBlock(hash)
|
||||
if result != test.want {
|
||||
t.Fatalf("HaveBlock #%d got %v want %v", i, result,
|
||||
t.Fatalf("IsKnownBlock #%d got %v want %v", i, result,
|
||||
test.want)
|
||||
}
|
||||
}
|
||||
@@ -241,9 +243,9 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
numBlocksToGenerate := 5
|
||||
for i := 0; i < numBlocksToGenerate; i++ {
|
||||
blockTime = blockTime.Add(time.Second)
|
||||
node = newTestNode(dag, setFromSlice(node), blockVersion, 0, blockTime)
|
||||
node = newTestNode(dag, blockSetFromSlice(node), blockVersion, 0, blockTime)
|
||||
dag.index.AddNode(node)
|
||||
dag.virtual.SetTips(setFromSlice(node))
|
||||
dag.virtual.SetTips(blockSetFromSlice(node))
|
||||
}
|
||||
|
||||
// Create a utxo view with a fake utxo for the inputs used in the
|
||||
@@ -509,7 +511,7 @@ func TestCalcPastMedianTime(t *testing.T) {
|
||||
blockTime := dag.genesis.Header().Timestamp
|
||||
for i := uint32(1); i < numBlocks; i++ {
|
||||
blockTime = blockTime.Add(time.Second)
|
||||
nodes[i] = newTestNode(dag, setFromSlice(nodes[i-1]), blockVersion, 0, blockTime)
|
||||
nodes[i] = newTestNode(dag, blockSetFromSlice(nodes[i-1]), blockVersion, 0, blockTime)
|
||||
dag.index.AddNode(nodes[i])
|
||||
}
|
||||
|
||||
@@ -544,15 +546,9 @@ func TestCalcPastMedianTime(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
// Create the root directory for test databases.
|
||||
if !FileExists(testDbRoot) {
|
||||
if err := os.MkdirAll(testDbRoot, 0700); err != nil {
|
||||
t.Fatalf("unable to create test db "+
|
||||
"root: %s", err)
|
||||
}
|
||||
}
|
||||
tempDir := os.TempDir()
|
||||
|
||||
dbPath := filepath.Join(testDbRoot, "TestNew")
|
||||
dbPath := filepath.Join(tempDir, "TestNew")
|
||||
_ = os.RemoveAll(dbPath)
|
||||
db, err := database.Create(testDbType, dbPath, blockDataNet)
|
||||
if err != nil {
|
||||
@@ -561,7 +557,6 @@ func TestNew(t *testing.T) {
|
||||
defer func() {
|
||||
db.Close()
|
||||
os.RemoveAll(dbPath)
|
||||
os.RemoveAll(testDbRoot)
|
||||
}()
|
||||
config := &Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
@@ -590,16 +585,10 @@ func TestNew(t *testing.T) {
|
||||
// occur when the node shuts down improperly while a block is being
|
||||
// validated.
|
||||
func TestAcceptingInInit(t *testing.T) {
|
||||
// Create the root directory for test databases.
|
||||
if !FileExists(testDbRoot) {
|
||||
if err := os.MkdirAll(testDbRoot, 0700); err != nil {
|
||||
t.Fatalf("unable to create test db "+
|
||||
"root: %s", err)
|
||||
}
|
||||
}
|
||||
tempDir := os.TempDir()
|
||||
|
||||
// Create a test database
|
||||
dbPath := filepath.Join(testDbRoot, "TestAcceptingInInit")
|
||||
dbPath := filepath.Join(tempDir, "TestAcceptingInInit")
|
||||
_ = os.RemoveAll(dbPath)
|
||||
db, err := database.Create(testDbType, dbPath, blockDataNet)
|
||||
if err != nil {
|
||||
@@ -608,7 +597,6 @@ func TestAcceptingInInit(t *testing.T) {
|
||||
defer func() {
|
||||
db.Close()
|
||||
os.RemoveAll(dbPath)
|
||||
os.RemoveAll(testDbRoot)
|
||||
}()
|
||||
|
||||
// Create a DAG to add the test block into
|
||||
@@ -633,7 +621,7 @@ func TestAcceptingInInit(t *testing.T) {
|
||||
|
||||
// Create a test blockNode with an unvalidated status
|
||||
genesisNode := dag.index.LookupNode(genesisBlock.Hash())
|
||||
testNode, _ := dag.newBlockNode(&testBlock.MsgBlock().Header, setFromSlice(genesisNode))
|
||||
testNode, _ := dag.newBlockNode(&testBlock.MsgBlock().Header, blockSetFromSlice(genesisNode))
|
||||
testNode.status = statusDataStored
|
||||
|
||||
// Manually add the test block to the database
|
||||
@@ -922,7 +910,7 @@ func testFinalizeNodesBelowFinalityPoint(t *testing.T, deleteDiffData bool) {
|
||||
|
||||
addNode := func(parent *blockNode) *blockNode {
|
||||
blockTime = blockTime.Add(time.Second)
|
||||
node := newTestNode(dag, setFromSlice(parent), blockVersion, 0, blockTime)
|
||||
node := newTestNode(dag, blockSetFromSlice(parent), blockVersion, 0, blockTime)
|
||||
node.updateParentsChildren()
|
||||
dag.index.AddNode(node)
|
||||
|
||||
@@ -1008,7 +996,7 @@ func TestDAGIndexFailedStatus(t *testing.T) {
|
||||
invalidBlock := util.NewBlock(invalidMsgBlock)
|
||||
isOrphan, isDelayed, err := dag.ProcessBlock(invalidBlock, BFNoPoWCheck)
|
||||
|
||||
if _, ok := err.(RuleError); !ok {
|
||||
if !errors.As(err, &RuleError{}) {
|
||||
t.Fatalf("ProcessBlock: expected a rule error but got %s instead", err)
|
||||
}
|
||||
if isDelayed {
|
||||
@@ -1037,7 +1025,8 @@ func TestDAGIndexFailedStatus(t *testing.T) {
|
||||
invalidBlockChild := util.NewBlock(invalidMsgBlockChild)
|
||||
|
||||
isOrphan, isDelayed, err = dag.ProcessBlock(invalidBlockChild, BFNoPoWCheck)
|
||||
if rErr, ok := err.(RuleError); !ok || rErr.ErrorCode != ErrInvalidAncestorBlock {
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); !ok || ruleErr.ErrorCode != ErrInvalidAncestorBlock {
|
||||
t.Fatalf("ProcessBlock: expected a rule error but got %s instead", err)
|
||||
}
|
||||
if isDelayed {
|
||||
@@ -1065,7 +1054,7 @@ func TestDAGIndexFailedStatus(t *testing.T) {
|
||||
invalidBlockGrandChild := util.NewBlock(invalidMsgBlockGrandChild)
|
||||
|
||||
isOrphan, isDelayed, err = dag.ProcessBlock(invalidBlockGrandChild, BFNoPoWCheck)
|
||||
if rErr, ok := err.(RuleError); !ok || rErr.ErrorCode != ErrInvalidAncestorBlock {
|
||||
if ok := errors.As(err, &ruleErr); !ok || ruleErr.ErrorCode != ErrInvalidAncestorBlock {
|
||||
t.Fatalf("ProcessBlock: expected a rule error but got %s instead", err)
|
||||
}
|
||||
if isDelayed {
|
||||
|
||||
@@ -83,8 +83,8 @@ func (e errNotInDAG) Error() string {
|
||||
// isNotInDAGErr returns whether or not the passed error is an
|
||||
// errNotInDAG error.
|
||||
func isNotInDAGErr(err error) bool {
|
||||
_, ok := err.(errNotInDAG)
|
||||
return ok
|
||||
var notInDAGErr errNotInDAG
|
||||
return errors.As(err, ¬InDAGErr)
|
||||
}
|
||||
|
||||
// errDeserialize signifies that a problem was encountered when deserializing
|
||||
@@ -99,8 +99,8 @@ func (e errDeserialize) Error() string {
|
||||
// isDeserializeErr returns whether or not the passed error is an errDeserialize
|
||||
// error.
|
||||
func isDeserializeErr(err error) bool {
|
||||
_, ok := err.(errDeserialize)
|
||||
return ok
|
||||
var deserializeErr errDeserialize
|
||||
return errors.As(err, &deserializeErr)
|
||||
}
|
||||
|
||||
// dbPutVersion uses an existing database transaction to update the provided
|
||||
@@ -577,7 +577,7 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
}
|
||||
|
||||
// Apply the stored tips to the virtual block.
|
||||
tips := newSet()
|
||||
tips := newBlockSet()
|
||||
for _, tipHash := range state.TipHashes {
|
||||
tip := dag.index.LookupNode(tipHash)
|
||||
if tip == nil {
|
||||
@@ -608,6 +608,9 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
|
||||
// Attempt to accept the block.
|
||||
block, err := dbFetchBlockByNode(dbTx, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isOrphan, isDelayed, err := dag.ProcessBlock(block, BFWasStored)
|
||||
if err != nil {
|
||||
log.Warnf("Block %s, which was not previously processed, "+
|
||||
@@ -654,8 +657,8 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
utxoCommitment: header.UTXOCommitment,
|
||||
}
|
||||
|
||||
node.children = newSet()
|
||||
node.parents = newSet()
|
||||
node.children = newBlockSet()
|
||||
node.parents = newBlockSet()
|
||||
|
||||
for _, hash := range header.ParentHashes {
|
||||
parent := dag.index.LookupNode(hash)
|
||||
@@ -706,17 +709,21 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node.bluesAnticoneSizes = make(map[daghash.Hash]dagconfig.KType)
|
||||
node.bluesAnticoneSizes = make(map[*blockNode]dagconfig.KType)
|
||||
for i := uint64(0); i < bluesAnticoneSizesLen; i++ {
|
||||
hash := &daghash.Hash{}
|
||||
if _, err := io.ReadFull(buffer, hash[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bluesAnticoneSize, err := binaryserializer.Uint8(buffer)
|
||||
node.bluesAnticoneSizes[*hash] = dagconfig.KType(bluesAnticoneSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
@@ -784,8 +791,8 @@ func serializeBlockNode(node *blockNode) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for blockHash, blueAnticoneSize := range node.bluesAnticoneSizes {
|
||||
_, err = w.Write(blockHash[:])
|
||||
for blue, blueAnticoneSize := range node.bluesAnticoneSizes {
|
||||
_, err = w.Write(blue.hash[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -873,7 +880,7 @@ func (dag *BlockDAG) BlockHashesFrom(lowHash *daghash.Hash, limit int) ([]*dagha
|
||||
// genesis hash in the result
|
||||
blockHashes = append(blockHashes, dag.genesis.hash)
|
||||
}
|
||||
if !dag.BlockExists(lowHash) {
|
||||
if !dag.IsInDAG(lowHash) {
|
||||
return nil, errors.Errorf("block %s not found", lowHash)
|
||||
}
|
||||
blueScore, err := dag.BlueScoreByBlockHash(lowHash)
|
||||
|
||||
@@ -248,12 +248,13 @@ func TestDAGStateDeserializeErrors(t *testing.T) {
|
||||
test.name, err, test.errType)
|
||||
continue
|
||||
}
|
||||
if derr, ok := err.(database.Error); ok {
|
||||
var dbErr database.Error
|
||||
if ok := errors.As(err, &dbErr); ok {
|
||||
tderr := test.errType.(database.Error)
|
||||
if derr.ErrorCode != tderr.ErrorCode {
|
||||
if dbErr.ErrorCode != tderr.ErrorCode {
|
||||
t.Errorf("deserializeDAGState (%s): "+
|
||||
"wrong error code got: %v, want: %v",
|
||||
test.name, derr.ErrorCode,
|
||||
test.name, dbErr.ErrorCode,
|
||||
tderr.ErrorCode)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -117,28 +117,28 @@ func TestDifficulty(t *testing.T) {
|
||||
}
|
||||
tip := dag.genesis
|
||||
for i := uint64(0); i < dag.difficultyAdjustmentWindowSize; i++ {
|
||||
tip = addNode(setFromSlice(tip), zeroTime)
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if tip.bits != dag.genesis.bits {
|
||||
t.Fatalf("As long as the bluest parent's blue score is less then the difficulty adjustment window size, the difficulty should be the same as genesis'")
|
||||
}
|
||||
}
|
||||
for i := uint64(0); i < dag.difficultyAdjustmentWindowSize+100; i++ {
|
||||
tip = addNode(setFromSlice(tip), zeroTime)
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if tip.bits != dag.genesis.bits {
|
||||
t.Fatalf("As long as the block rate remains the same, the difficulty shouldn't change")
|
||||
}
|
||||
}
|
||||
nodeInThePast := addNode(setFromSlice(tip), tip.PastMedianTime(dag))
|
||||
nodeInThePast := addNode(blockSetFromSlice(tip), tip.PastMedianTime(dag))
|
||||
if nodeInThePast.bits != tip.bits {
|
||||
t.Fatalf("The difficulty should only change when nodeInThePast is in the past of a block bluest parent")
|
||||
}
|
||||
tip = nodeInThePast
|
||||
|
||||
tip = addNode(setFromSlice(tip), zeroTime)
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if tip.bits != nodeInThePast.bits {
|
||||
t.Fatalf("The difficulty should only change when nodeInThePast is in the past of a block bluest parent")
|
||||
}
|
||||
tip = addNode(setFromSlice(tip), zeroTime)
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if compareBits(tip.bits, nodeInThePast.bits) >= 0 {
|
||||
t.Fatalf("tip.bits should be smaller than nodeInThePast.bits because nodeInThePast increased the block rate, so the difficulty should increase as well")
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func TestDifficulty(t *testing.T) {
|
||||
|
||||
// Increase block rate to increase difficulty
|
||||
for i := uint64(0); i < dag.difficultyAdjustmentWindowSize; i++ {
|
||||
tip = addNode(setFromSlice(tip), tip.PastMedianTime(dag))
|
||||
tip = addNode(blockSetFromSlice(tip), tip.PastMedianTime(dag))
|
||||
if compareBits(tip.bits, tip.parents.bluest().bits) > 0 {
|
||||
t.Fatalf("Because we're increasing the block rate, the difficulty can't decrease")
|
||||
}
|
||||
@@ -159,7 +159,7 @@ func TestDifficulty(t *testing.T) {
|
||||
lastBits := tip.bits
|
||||
sameBitsCount := uint64(0)
|
||||
for sameBitsCount < dag.difficultyAdjustmentWindowSize+1 {
|
||||
tip = addNode(setFromSlice(tip), zeroTime)
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if tip.bits == lastBits {
|
||||
sameBitsCount++
|
||||
} else {
|
||||
@@ -167,35 +167,35 @@ func TestDifficulty(t *testing.T) {
|
||||
sameBitsCount = 0
|
||||
}
|
||||
}
|
||||
slowNode := addNode(setFromSlice(tip), time.Unix(tip.timestamp+2, 0))
|
||||
slowNode := addNode(blockSetFromSlice(tip), time.Unix(tip.timestamp+2, 0))
|
||||
if slowNode.bits != tip.bits {
|
||||
t.Fatalf("The difficulty should only change when slowNode is in the past of a block bluest parent")
|
||||
}
|
||||
|
||||
tip = slowNode
|
||||
|
||||
tip = addNode(setFromSlice(tip), zeroTime)
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if tip.bits != slowNode.bits {
|
||||
t.Fatalf("The difficulty should only change when slowNode is in the past of a block bluest parent")
|
||||
}
|
||||
tip = addNode(setFromSlice(tip), zeroTime)
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if compareBits(tip.bits, slowNode.bits) <= 0 {
|
||||
t.Fatalf("tip.bits should be smaller than slowNode.bits because slowNode decreased the block rate, so the difficulty should decrease as well")
|
||||
}
|
||||
|
||||
splitNode := addNode(setFromSlice(tip), zeroTime)
|
||||
splitNode := addNode(blockSetFromSlice(tip), zeroTime)
|
||||
tip = splitNode
|
||||
for i := 0; i < 100; i++ {
|
||||
tip = addNode(setFromSlice(tip), zeroTime)
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
}
|
||||
blueTip := tip
|
||||
|
||||
redChainTip := splitNode
|
||||
for i := 0; i < 10; i++ {
|
||||
redChainTip = addNode(setFromSlice(redChainTip), redChainTip.PastMedianTime(dag))
|
||||
redChainTip = addNode(blockSetFromSlice(redChainTip), redChainTip.PastMedianTime(dag))
|
||||
}
|
||||
tipWithRedPast := addNode(setFromSlice(redChainTip, blueTip), zeroTime)
|
||||
tipWithoutRedPast := addNode(setFromSlice(blueTip), zeroTime)
|
||||
tipWithRedPast := addNode(blockSetFromSlice(redChainTip, blueTip), zeroTime)
|
||||
tipWithoutRedPast := addNode(blockSetFromSlice(blueTip), zeroTime)
|
||||
if tipWithoutRedPast.bits != tipWithRedPast.bits {
|
||||
t.Fatalf("tipWithoutRedPast.bits should be the same as tipWithRedPast.bits because red blocks shouldn't affect the difficulty")
|
||||
}
|
||||
|
||||
@@ -217,6 +217,10 @@ const (
|
||||
// ErrInvalidParentsRelation indicates that one of the parents of a block
|
||||
// is also an ancestor of another parent
|
||||
ErrInvalidParentsRelation
|
||||
|
||||
// ErrDelayedBlockIsNotAllowed indicates that a block with a delayed timestamp was
|
||||
// submitted with BFDisallowDelay flag raised.
|
||||
ErrDelayedBlockIsNotAllowed
|
||||
)
|
||||
|
||||
// Map of ErrorCode values back to their constant names for pretty printing.
|
||||
@@ -264,6 +268,7 @@ var errorCodeStrings = map[ErrorCode]string{
|
||||
ErrInvalidPayload: "ErrInvalidPayload",
|
||||
ErrInvalidPayloadHash: "ErrInvalidPayloadHash",
|
||||
ErrInvalidParentsRelation: "ErrInvalidParentsRelation",
|
||||
ErrDelayedBlockIsNotAllowed: "ErrDelayedBlockIsNotAllowed",
|
||||
}
|
||||
|
||||
// String returns the ErrorCode as a human-readable name.
|
||||
|
||||
@@ -58,6 +58,7 @@ func TestErrorCodeStringer(t *testing.T) {
|
||||
{ErrInvalidPayload, "ErrInvalidPayload"},
|
||||
{ErrInvalidPayloadHash, "ErrInvalidPayloadHash"},
|
||||
{ErrInvalidParentsRelation, "ErrInvalidParentsRelation"},
|
||||
{ErrDelayedBlockIsNotAllowed, "ErrDelayedBlockIsNotAllowed"},
|
||||
{0xffff, "Unknown ErrorCode (65535)"},
|
||||
}
|
||||
|
||||
|
||||
@@ -137,10 +137,10 @@ func TestFinality(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Errorf("TestFinality: buildNodeToDag expected an error but got <nil>")
|
||||
}
|
||||
rErr, ok := err.(blockdag.RuleError)
|
||||
if ok {
|
||||
if rErr.ErrorCode != blockdag.ErrFinality {
|
||||
t.Errorf("TestFinality: buildNodeToDag expected an error with code %v but instead got %v", blockdag.ErrFinality, rErr.ErrorCode)
|
||||
var ruleErr blockdag.RuleError
|
||||
if errors.As(err, &ruleErr) {
|
||||
if ruleErr.ErrorCode != blockdag.ErrFinality {
|
||||
t.Errorf("TestFinality: buildNodeToDag expected an error with code %v but instead got %v", blockdag.ErrFinality, ruleErr.ErrorCode)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("TestFinality: buildNodeToDag got unexpected error: %v", err)
|
||||
@@ -152,13 +152,12 @@ func TestFinality(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Errorf("TestFinality: buildNodeToDag expected an error but got <nil>")
|
||||
}
|
||||
rErr, ok = err.(blockdag.RuleError)
|
||||
if ok {
|
||||
if rErr.ErrorCode != blockdag.ErrFinality {
|
||||
t.Errorf("TestFinality: buildNodeToDag expected an error with code %v but instead got %v", blockdag.ErrFinality, rErr.ErrorCode)
|
||||
if errors.As(err, &ruleErr) {
|
||||
if ruleErr.ErrorCode != blockdag.ErrFinality {
|
||||
t.Errorf("TestFinality: buildNodeToDag expected an error with code %v but instead got %v", blockdag.ErrFinality, ruleErr.ErrorCode)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("TestFinality: buildNodeToDag got unexpected error: %v", rErr)
|
||||
t.Errorf("TestFinality: buildNodeToDag got unexpected error: %v", ruleErr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,12 +276,15 @@ func TestChainedTransactions(t *testing.T) {
|
||||
isOrphan, isDelayed, err = dag.ProcessBlock(util.NewBlock(block2), blockdag.BFNoPoWCheck)
|
||||
if err == nil {
|
||||
t.Errorf("ProcessBlock expected an error")
|
||||
} else if rErr, ok := err.(blockdag.RuleError); ok {
|
||||
if rErr.ErrorCode != blockdag.ErrMissingTxOut {
|
||||
t.Errorf("ProcessBlock expected an %v error code but got %v", blockdag.ErrMissingTxOut, rErr.ErrorCode)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("ProcessBlock expected a blockdag.RuleError but got %v", err)
|
||||
var ruleErr blockdag.RuleError
|
||||
if ok := errors.As(err, &ruleErr); ok {
|
||||
if ruleErr.ErrorCode != blockdag.ErrMissingTxOut {
|
||||
t.Errorf("ProcessBlock expected an %v error code but got %v", blockdag.ErrMissingTxOut, ruleErr.ErrorCode)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("ProcessBlock expected a blockdag.RuleError but got %v", err)
|
||||
}
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("ProcessBlock: block2 " +
|
||||
@@ -476,11 +478,11 @@ func TestGasLimit(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatalf("ProcessBlock expected to have an error in block that exceeds gas limit")
|
||||
}
|
||||
rErr, ok := err.(blockdag.RuleError)
|
||||
if !ok {
|
||||
var ruleErr blockdag.RuleError
|
||||
if !errors.As(err, &ruleErr) {
|
||||
t.Fatalf("ProcessBlock expected a RuleError, but got %v", err)
|
||||
} else if rErr.ErrorCode != blockdag.ErrInvalidGas {
|
||||
t.Fatalf("ProcessBlock expected error code %s but got %s", blockdag.ErrInvalidGas, rErr.ErrorCode)
|
||||
} else if ruleErr.ErrorCode != blockdag.ErrInvalidGas {
|
||||
t.Fatalf("ProcessBlock expected error code %s but got %s", blockdag.ErrInvalidGas, ruleErr.ErrorCode)
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("ProcessBlock: overLimitBlock " +
|
||||
@@ -511,15 +513,18 @@ func TestGasLimit(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatalf("ProcessBlock expected to have an error")
|
||||
}
|
||||
rErr, ok = err.(blockdag.RuleError)
|
||||
if !ok {
|
||||
if !errors.As(err, &ruleErr) {
|
||||
t.Fatalf("ProcessBlock expected a RuleError, but got %v", err)
|
||||
} else if rErr.ErrorCode != blockdag.ErrInvalidGas {
|
||||
t.Fatalf("ProcessBlock expected error code %s but got %s", blockdag.ErrInvalidGas, rErr.ErrorCode)
|
||||
} else if ruleErr.ErrorCode != blockdag.ErrInvalidGas {
|
||||
t.Fatalf("ProcessBlock expected error code %s but got %s", blockdag.ErrInvalidGas, ruleErr.ErrorCode)
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("ProcessBlock: overLimitBlock got unexpectedly orphan")
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("ProcessBlock: overflowGasBlock " +
|
||||
"is too far in the future")
|
||||
}
|
||||
|
||||
nonExistentSubnetwork := &subnetworkid.SubnetworkID{123}
|
||||
nonExistentSubnetworkTxIn := &wire.TxIn{
|
||||
@@ -546,6 +551,13 @@ func TestGasLimit(t *testing.T) {
|
||||
if err.Error() != expectedErrStr {
|
||||
t.Fatalf("ProcessBlock expected error \"%v\" but got \"%v\"", expectedErrStr, err)
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("ProcessBlock: nonExistentSubnetworkBlock " +
|
||||
"is too far in the future")
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("ProcessBlock: nonExistentSubnetworkBlock got unexpectedly orphan")
|
||||
}
|
||||
|
||||
// Here we check that we can process a block with a transaction that doesn't exceed the gas limit
|
||||
validBlock, err := mining.PrepareBlockForTest(dag, ¶ms, dag.TipHashes(), []*wire.MsgTx{tx1}, true)
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
// For further details see the article https://eprint.iacr.org/2018/104.pdf
|
||||
func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blockNode, err error) {
|
||||
newNode.selectedParent = newNode.parents.bluest()
|
||||
newNode.bluesAnticoneSizes[*newNode.selectedParent.hash] = 0
|
||||
newNode.bluesAnticoneSizes[newNode.selectedParent] = 0
|
||||
newNode.blues = []*blockNode{newNode.selectedParent}
|
||||
selectedParentAnticone, err = dag.selectedParentAnticone(newNode)
|
||||
if err != nil {
|
||||
@@ -102,9 +102,9 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
|
||||
if possiblyBlue {
|
||||
// No k-cluster violation found, we can now set the candidate block as blue
|
||||
newNode.blues = append(newNode.blues, blueCandidate)
|
||||
newNode.bluesAnticoneSizes[*blueCandidate.hash] = candidateAnticoneSize
|
||||
newNode.bluesAnticoneSizes[blueCandidate] = candidateAnticoneSize
|
||||
for blue, blueAnticoneSize := range candidateBluesAnticoneSizes {
|
||||
newNode.bluesAnticoneSizes[*blue.hash] = blueAnticoneSize + 1
|
||||
newNode.bluesAnticoneSizes[blue] = blueAnticoneSize + 1
|
||||
}
|
||||
|
||||
// The maximum length of node.blues can be K+1 because
|
||||
@@ -126,9 +126,9 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
|
||||
// we check whether it is in the past of the selected parent.
|
||||
// If not, we add the node to the resulting anticone-set and queue it for processing.
|
||||
func (dag *BlockDAG) selectedParentAnticone(node *blockNode) ([]*blockNode, error) {
|
||||
anticoneSet := newSet()
|
||||
anticoneSet := newBlockSet()
|
||||
var anticoneSlice []*blockNode
|
||||
selectedParentPast := newSet()
|
||||
selectedParentPast := newBlockSet()
|
||||
var queue []*blockNode
|
||||
// Queueing all parents (other than the selected parent itself) for processing.
|
||||
for parent := range node.parents {
|
||||
@@ -168,7 +168,7 @@ func (dag *BlockDAG) selectedParentAnticone(node *blockNode) ([]*blockNode, erro
|
||||
// Expects 'block' to be in the blue set of 'context'
|
||||
func (dag *BlockDAG) blueAnticoneSize(block, context *blockNode) (dagconfig.KType, error) {
|
||||
for current := context; current != nil; current = current.selectedParent {
|
||||
if blueAnticoneSize, ok := current.bluesAnticoneSizes[*block.hash]; ok {
|
||||
if blueAnticoneSize, ok := current.bluesAnticoneSizes[block]; ok {
|
||||
return blueAnticoneSize, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,12 @@ package blockdag
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -276,3 +279,92 @@ func checkReds(expectedReds []string, reds map[string]bool) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestBlueAnticoneSizeErrors(t *testing.T) {
|
||||
// Create a new database and DAG instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestBlueAnticoneSizeErrors", Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("TestBlueAnticoneSizeErrors: Failed to setup DAG instance: %s", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Prepare a block chain with size K beginning with the genesis block
|
||||
currentBlockA := dag.dagParams.GenesisBlock
|
||||
for i := dagconfig.KType(0); i < dag.dagParams.K; i++ {
|
||||
newBlock := prepareAndProcessBlock(t, dag, currentBlockA)
|
||||
currentBlockA = newBlock
|
||||
}
|
||||
|
||||
// Prepare another block chain with size K beginning with the genesis block
|
||||
currentBlockB := dag.dagParams.GenesisBlock
|
||||
for i := dagconfig.KType(0); i < dag.dagParams.K; i++ {
|
||||
newBlock := prepareAndProcessBlock(t, dag, currentBlockB)
|
||||
currentBlockB = newBlock
|
||||
}
|
||||
|
||||
// Get references to the tips of the two chains
|
||||
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.
|
||||
_, err = dag.blueAnticoneSize(blockNodeA, blockNodeB)
|
||||
if err == nil {
|
||||
t.Fatalf("TestBlueAnticoneSizeErrors: blueAnticoneSize unexpectedly succeeded")
|
||||
}
|
||||
expectedErrSubstring := "is not in blue set of"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestBlueAnticoneSizeErrors: blueAnticoneSize returned wrong error. "+
|
||||
"Want: %s, got: %s", expectedErrSubstring, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGHOSTDAGErrors(t *testing.T) {
|
||||
// Create a new database and DAG instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestGHOSTDAGErrors", Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("TestGHOSTDAGErrors: Failed to setup DAG instance: %s", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Add two child blocks to the genesis
|
||||
block1 := prepareAndProcessBlock(t, dag, dag.dagParams.GenesisBlock)
|
||||
block2 := prepareAndProcessBlock(t, dag, dag.dagParams.GenesisBlock)
|
||||
|
||||
// Add a child block to the previous two blocks
|
||||
block3 := prepareAndProcessBlock(t, dag, block1, block2)
|
||||
|
||||
// Clear the reachability store
|
||||
dag.reachabilityStore.loaded = map[daghash.Hash]*reachabilityData{}
|
||||
err = dag.db.Update(func(dbTx database.Tx) error {
|
||||
bucket := dbTx.Metadata().Bucket(reachabilityDataBucketName)
|
||||
cursor := bucket.Cursor()
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() {
|
||||
err := bucket.Delete(cursor.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("TestGHOSTDAGErrors: db.Update failed: %s", err)
|
||||
}
|
||||
|
||||
// Try to rerun GHOSTDAG on the last block. GHOSTDAG uses
|
||||
// reachability data, so we expect it to fail.
|
||||
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"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestGHOSTDAGErrors: ghostdag returned wrong error. "+
|
||||
"Want: %s, got: %s", expectedErrSubstring, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
indexers
|
||||
========
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad/blockdag/indexers)
|
||||
|
||||
Package indexers implements optional block chain indexes.
|
||||
|
||||
@@ -298,16 +298,16 @@ func copyDirectory(scrDir, dest string) error {
|
||||
// This function is copied and modified from this stackoverflow answer: https://stackoverflow.com/a/56314145/2413761
|
||||
func copyFile(srcFile, dstFile string) error {
|
||||
out, err := os.Create(dstFile)
|
||||
defer out.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
in, err := os.Open(srcFile)
|
||||
defer in.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,902 +0,0 @@
|
||||
// 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 indexers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
const (
|
||||
// addrIndexName is the human-readable name for the index.
|
||||
addrIndexName = "address index"
|
||||
|
||||
// level0MaxEntries is the maximum number of transactions that are
|
||||
// stored in level 0 of an address index entry. Subsequent levels store
|
||||
// 2^n * level0MaxEntries entries, or in words, double the maximum of
|
||||
// the previous level.
|
||||
level0MaxEntries = 8
|
||||
|
||||
// addrKeySize is the number of bytes an address key consumes in the
|
||||
// index. It consists of 1 byte address type + 20 bytes hash160.
|
||||
addrKeySize = 1 + 20
|
||||
|
||||
// levelKeySize is the number of bytes a level key in the address index
|
||||
// consumes. It consists of the address key + 1 byte for the level.
|
||||
levelKeySize = addrKeySize + 1
|
||||
|
||||
// levelOffset is the offset in the level key which identifes the level.
|
||||
levelOffset = levelKeySize - 1
|
||||
|
||||
// addrKeyTypePubKeyHash is the address type in an address key which
|
||||
// represents both a pay-to-pubkey-hash and a pay-to-pubkey address.
|
||||
// This is done because both are identical for the purposes of the
|
||||
// address index.
|
||||
addrKeyTypePubKeyHash = 0
|
||||
|
||||
// addrKeyTypeScriptHash is the address type in an address key which
|
||||
// represents a pay-to-script-hash address. This is necessary because
|
||||
// the hash of a pubkey address might be the same as that of a script
|
||||
// hash.
|
||||
addrKeyTypeScriptHash = 1
|
||||
|
||||
// Size of a transaction entry. It consists of 8 bytes block id + 4
|
||||
// bytes offset + 4 bytes length.
|
||||
txEntrySize = 8 + 4 + 4
|
||||
)
|
||||
|
||||
var (
|
||||
// addrIndexKey is the key of the address index and the db bucket used
|
||||
// to house it.
|
||||
addrIndexKey = []byte("txbyaddridx")
|
||||
|
||||
// errUnsupportedAddressType is an error that is used to signal an
|
||||
// unsupported address type has been used.
|
||||
errUnsupportedAddressType = errors.New("address type is not supported " +
|
||||
"by the address index")
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The address index maps addresses referenced in the blockDAG to a list of
|
||||
// all the transactions involving that address. Transactions are stored
|
||||
// according to their order of appearance in the blockDAG. That is to say
|
||||
// first by block height and then by offset inside the block. It is also
|
||||
// important to note that this implementation requires the transaction index
|
||||
// since it is needed in order to catch up old blocks due to the fact the spent
|
||||
// outputs will already be pruned from the utxo set.
|
||||
//
|
||||
// The approach used to store the index is similar to a log-structured merge
|
||||
// tree (LSM tree) and is thus similar to how leveldb works internally.
|
||||
//
|
||||
// Every address consists of one or more entries identified by a level starting
|
||||
// from 0 where each level holds a maximum number of entries such that each
|
||||
// subsequent level holds double the maximum of the previous one. In equation
|
||||
// form, the number of entries each level holds is 2^n * firstLevelMaxSize.
|
||||
//
|
||||
// New transactions are appended to level 0 until it becomes full at which point
|
||||
// the entire level 0 entry is appended to the level 1 entry and level 0 is
|
||||
// cleared. This process continues until level 1 becomes full at which point it
|
||||
// will be appended to level 2 and cleared and so on.
|
||||
//
|
||||
// The result of this is the lower levels contain newer transactions and the
|
||||
// transactions within each level are ordered from oldest to newest.
|
||||
//
|
||||
// The intent of this approach is to provide a balance between space efficiency
|
||||
// and indexing cost. Storing one entry per transaction would have the lowest
|
||||
// indexing cost, but would waste a lot of space because the same address hash
|
||||
// would be duplicated for every transaction key. On the other hand, storing a
|
||||
// single entry with all transactions would be the most space efficient, but
|
||||
// would cause indexing cost to grow quadratically with the number of
|
||||
// transactions involving the same address. The approach used here provides
|
||||
// logarithmic insertion and retrieval.
|
||||
//
|
||||
// The serialized key format is:
|
||||
//
|
||||
// <addr type><addr hash><level>
|
||||
//
|
||||
// Field Type Size
|
||||
// addr type uint8 1 byte
|
||||
// addr hash hash160 20 bytes
|
||||
// level uint8 1 byte
|
||||
// -----
|
||||
// Total: 22 bytes
|
||||
//
|
||||
// The serialized value format is:
|
||||
//
|
||||
// [<block id><start offset><tx length>,...]
|
||||
//
|
||||
// Field Type Size
|
||||
// block id uint64 8 bytes
|
||||
// start offset uint32 4 bytes
|
||||
// tx length uint32 4 bytes
|
||||
// -----
|
||||
// Total: 16 bytes per indexed tx
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// fetchBlockHashFunc defines a callback function to use in order to convert a
|
||||
// serialized block ID to an associated block hash.
|
||||
type fetchBlockHashFunc func(serializedID []byte) (*daghash.Hash, error)
|
||||
|
||||
// serializeAddrIndexEntry serializes the provided block id and transaction
|
||||
// location according to the format described in detail above.
|
||||
func serializeAddrIndexEntry(blockID uint64, txLoc wire.TxLoc) []byte {
|
||||
// Serialize the entry.
|
||||
serialized := make([]byte, 16)
|
||||
byteOrder.PutUint64(serialized, blockID)
|
||||
byteOrder.PutUint32(serialized[8:], uint32(txLoc.TxStart))
|
||||
byteOrder.PutUint32(serialized[12:], uint32(txLoc.TxLen))
|
||||
return serialized
|
||||
}
|
||||
|
||||
// deserializeAddrIndexEntry decodes the passed serialized byte slice into the
|
||||
// provided region struct according to the format described in detail above and
|
||||
// uses the passed block hash fetching function in order to conver the block ID
|
||||
// to the associated block hash.
|
||||
func deserializeAddrIndexEntry(serialized []byte, region *database.BlockRegion, fetchBlockHash fetchBlockHashFunc) error {
|
||||
// Ensure there are enough bytes to decode.
|
||||
if len(serialized) < txEntrySize {
|
||||
return errDeserialize("unexpected end of data")
|
||||
}
|
||||
|
||||
hash, err := fetchBlockHash(serialized[0:8])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
region.Hash = hash
|
||||
region.Offset = byteOrder.Uint32(serialized[8:12])
|
||||
region.Len = byteOrder.Uint32(serialized[12:16])
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyForLevel returns the key for a specific address and level in the address
|
||||
// index entry.
|
||||
func keyForLevel(addrKey [addrKeySize]byte, level uint8) [levelKeySize]byte {
|
||||
var key [levelKeySize]byte
|
||||
copy(key[:], addrKey[:])
|
||||
key[levelOffset] = level
|
||||
return key
|
||||
}
|
||||
|
||||
// dbPutAddrIndexEntry updates the address index to include the provided entry
|
||||
// according to the level-based scheme described in detail above.
|
||||
func dbPutAddrIndexEntry(bucket internalBucket, addrKey [addrKeySize]byte, blockID uint64, txLoc wire.TxLoc) error {
|
||||
// Start with level 0 and its initial max number of entries.
|
||||
curLevel := uint8(0)
|
||||
maxLevelBytes := level0MaxEntries * txEntrySize
|
||||
|
||||
// Simply append the new entry to level 0 and return now when it will
|
||||
// fit. This is the most common path.
|
||||
newData := serializeAddrIndexEntry(blockID, txLoc)
|
||||
level0Key := keyForLevel(addrKey, 0)
|
||||
level0Data := bucket.Get(level0Key[:])
|
||||
if len(level0Data)+len(newData) <= maxLevelBytes {
|
||||
mergedData := newData
|
||||
if len(level0Data) > 0 {
|
||||
mergedData = make([]byte, len(level0Data)+len(newData))
|
||||
copy(mergedData, level0Data)
|
||||
copy(mergedData[len(level0Data):], newData)
|
||||
}
|
||||
return bucket.Put(level0Key[:], mergedData)
|
||||
}
|
||||
|
||||
// At this point, level 0 is full, so merge each level into higher
|
||||
// levels as many times as needed to free up level 0.
|
||||
prevLevelData := level0Data
|
||||
for {
|
||||
// Each new level holds twice as much as the previous one.
|
||||
curLevel++
|
||||
maxLevelBytes *= 2
|
||||
|
||||
// Move to the next level as long as the current level is full.
|
||||
curLevelKey := keyForLevel(addrKey, curLevel)
|
||||
curLevelData := bucket.Get(curLevelKey[:])
|
||||
if len(curLevelData) == maxLevelBytes {
|
||||
prevLevelData = curLevelData
|
||||
continue
|
||||
}
|
||||
|
||||
// The current level has room for the data in the previous one,
|
||||
// so merge the data from previous level into it.
|
||||
mergedData := prevLevelData
|
||||
if len(curLevelData) > 0 {
|
||||
mergedData = make([]byte, len(curLevelData)+
|
||||
len(prevLevelData))
|
||||
copy(mergedData, curLevelData)
|
||||
copy(mergedData[len(curLevelData):], prevLevelData)
|
||||
}
|
||||
err := bucket.Put(curLevelKey[:], mergedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move all of the levels before the previous one up a level.
|
||||
for mergeLevel := curLevel - 1; mergeLevel > 0; mergeLevel-- {
|
||||
mergeLevelKey := keyForLevel(addrKey, mergeLevel)
|
||||
prevLevelKey := keyForLevel(addrKey, mergeLevel-1)
|
||||
prevData := bucket.Get(prevLevelKey[:])
|
||||
err := bucket.Put(mergeLevelKey[:], prevData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Finally, insert the new entry into level 0 now that it is empty.
|
||||
return bucket.Put(level0Key[:], newData)
|
||||
}
|
||||
|
||||
// dbFetchAddrIndexEntries returns block regions for transactions referenced by
|
||||
// the given address key and the number of entries skipped since it could have
|
||||
// been less in the case where there are less total entries than the requested
|
||||
// number of entries to skip.
|
||||
func dbFetchAddrIndexEntries(bucket internalBucket, addrKey [addrKeySize]byte, numToSkip, numRequested uint32, reverse bool, fetchBlockHash fetchBlockHashFunc) ([]database.BlockRegion, uint32, error) {
|
||||
// When the reverse flag is not set, all levels need to be fetched
|
||||
// because numToSkip and numRequested are counted from the oldest
|
||||
// transactions (highest level) and thus the total count is needed.
|
||||
// However, when the reverse flag is set, only enough records to satisfy
|
||||
// the requested amount are needed.
|
||||
var level uint8
|
||||
var serialized []byte
|
||||
for !reverse || len(serialized) < int(numToSkip+numRequested)*txEntrySize {
|
||||
curLevelKey := keyForLevel(addrKey, level)
|
||||
levelData := bucket.Get(curLevelKey[:])
|
||||
if levelData == nil {
|
||||
// Stop when there are no more levels.
|
||||
break
|
||||
}
|
||||
|
||||
// Higher levels contain older transactions, so prepend them.
|
||||
prepended := make([]byte, len(serialized)+len(levelData))
|
||||
copy(prepended, levelData)
|
||||
copy(prepended[len(levelData):], serialized)
|
||||
serialized = prepended
|
||||
level++
|
||||
}
|
||||
|
||||
// When the requested number of entries to skip is larger than the
|
||||
// number available, skip them all and return now with the actual number
|
||||
// skipped.
|
||||
numEntries := uint32(len(serialized) / txEntrySize)
|
||||
if numToSkip >= numEntries {
|
||||
return nil, numEntries, nil
|
||||
}
|
||||
|
||||
// Nothing more to do when there are no requested entries.
|
||||
if numRequested == 0 {
|
||||
return nil, numToSkip, nil
|
||||
}
|
||||
|
||||
// Limit the number to load based on the number of available entries,
|
||||
// the number to skip, and the number requested.
|
||||
numToLoad := numEntries - numToSkip
|
||||
if numToLoad > numRequested {
|
||||
numToLoad = numRequested
|
||||
}
|
||||
|
||||
// Start the offset after all skipped entries and load the calculated
|
||||
// number.
|
||||
results := make([]database.BlockRegion, numToLoad)
|
||||
for i := uint32(0); i < numToLoad; i++ {
|
||||
// Calculate the read offset according to the reverse flag.
|
||||
var offset uint32
|
||||
if reverse {
|
||||
offset = (numEntries - numToSkip - i - 1) * txEntrySize
|
||||
} else {
|
||||
offset = (numToSkip + i) * txEntrySize
|
||||
}
|
||||
|
||||
// Deserialize and populate the result.
|
||||
err := deserializeAddrIndexEntry(serialized[offset:],
|
||||
&results[i], fetchBlockHash)
|
||||
if err != nil {
|
||||
// Ensure any deserialization errors are returned as
|
||||
// database corruption errors.
|
||||
if isDeserializeErr(err) {
|
||||
err = database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("failed to "+
|
||||
"deserialized address index "+
|
||||
"for key %x: %s", addrKey, err),
|
||||
}
|
||||
}
|
||||
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return results, numToSkip, nil
|
||||
}
|
||||
|
||||
// minEntriesToReachLevel returns the minimum number of entries that are
|
||||
// required to reach the given address index level.
|
||||
func minEntriesToReachLevel(level uint8) int {
|
||||
maxEntriesForLevel := level0MaxEntries
|
||||
minRequired := 1
|
||||
for l := uint8(1); l <= level; l++ {
|
||||
minRequired += maxEntriesForLevel
|
||||
maxEntriesForLevel *= 2
|
||||
}
|
||||
return minRequired
|
||||
}
|
||||
|
||||
// maxEntriesForLevel returns the maximum number of entries allowed for the
|
||||
// given address index level.
|
||||
func maxEntriesForLevel(level uint8) int {
|
||||
numEntries := level0MaxEntries
|
||||
for l := level; l > 0; l-- {
|
||||
numEntries *= 2
|
||||
}
|
||||
return numEntries
|
||||
}
|
||||
|
||||
// dbRemoveAddrIndexEntries removes the specified number of entries from from
|
||||
// the address index for the provided key. An assertion error will be returned
|
||||
// if the count exceeds the total number of entries in the index.
|
||||
func dbRemoveAddrIndexEntries(bucket internalBucket, addrKey [addrKeySize]byte, count int) error {
|
||||
// Nothing to do if no entries are being deleted.
|
||||
if count <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make use of a local map to track pending updates and define a closure
|
||||
// to apply it to the database. This is done in order to reduce the
|
||||
// number of database reads and because there is more than one exit
|
||||
// path that needs to apply the updates.
|
||||
pendingUpdates := make(map[uint8][]byte)
|
||||
applyPending := func() error {
|
||||
for level, data := range pendingUpdates {
|
||||
curLevelKey := keyForLevel(addrKey, level)
|
||||
if len(data) == 0 {
|
||||
err := bucket.Delete(curLevelKey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
err := bucket.Put(curLevelKey[:], data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loop forwards through the levels while removing entries until the
|
||||
// specified number has been removed. This will potentially result in
|
||||
// entirely empty lower levels which will be backfilled below.
|
||||
var highestLoadedLevel uint8
|
||||
numRemaining := count
|
||||
for level := uint8(0); numRemaining > 0; level++ {
|
||||
// Load the data for the level from the database.
|
||||
curLevelKey := keyForLevel(addrKey, level)
|
||||
curLevelData := bucket.Get(curLevelKey[:])
|
||||
if len(curLevelData) == 0 && numRemaining > 0 {
|
||||
return AssertError(fmt.Sprintf("dbRemoveAddrIndexEntries "+
|
||||
"not enough entries for address key %x to "+
|
||||
"delete %d entries", addrKey, count))
|
||||
}
|
||||
pendingUpdates[level] = curLevelData
|
||||
highestLoadedLevel = level
|
||||
|
||||
// Delete the entire level as needed.
|
||||
numEntries := len(curLevelData) / txEntrySize
|
||||
if numRemaining >= numEntries {
|
||||
pendingUpdates[level] = nil
|
||||
numRemaining -= numEntries
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove remaining entries to delete from the level.
|
||||
offsetEnd := len(curLevelData) - (numRemaining * txEntrySize)
|
||||
pendingUpdates[level] = curLevelData[:offsetEnd]
|
||||
break
|
||||
}
|
||||
|
||||
// When all elements in level 0 were not removed there is nothing left
|
||||
// to do other than updating the database.
|
||||
if len(pendingUpdates[0]) != 0 {
|
||||
return applyPending()
|
||||
}
|
||||
|
||||
// At this point there are one or more empty levels before the current
|
||||
// level which need to be backfilled and the current level might have
|
||||
// had some entries deleted from it as well. Since all levels after
|
||||
// level 0 are required to either be empty, half full, or completely
|
||||
// full, the current level must be adjusted accordingly by backfilling
|
||||
// each previous levels in a way which satisfies the requirements. Any
|
||||
// entries that are left are assigned to level 0 after the loop as they
|
||||
// are guaranteed to fit by the logic in the loop. In other words, this
|
||||
// effectively squashes all remaining entries in the current level into
|
||||
// the lowest possible levels while following the level rules.
|
||||
//
|
||||
// Note that the level after the current level might also have entries
|
||||
// and gaps are not allowed, so this also keeps track of the lowest
|
||||
// empty level so the code below knows how far to backfill in case it is
|
||||
// required.
|
||||
lowestEmptyLevel := uint8(255)
|
||||
curLevelData := pendingUpdates[highestLoadedLevel]
|
||||
curLevelMaxEntries := maxEntriesForLevel(highestLoadedLevel)
|
||||
for level := highestLoadedLevel; level > 0; level-- {
|
||||
// When there are not enough entries left in the current level
|
||||
// for the number that would be required to reach it, clear the
|
||||
// the current level which effectively moves them all up to the
|
||||
// previous level on the next iteration. Otherwise, there are
|
||||
// are sufficient entries, so update the current level to
|
||||
// contain as many entries as possible while still leaving
|
||||
// enough remaining entries required to reach the level.
|
||||
numEntries := len(curLevelData) / txEntrySize
|
||||
prevLevelMaxEntries := curLevelMaxEntries / 2
|
||||
minPrevRequired := minEntriesToReachLevel(level - 1)
|
||||
if numEntries < prevLevelMaxEntries+minPrevRequired {
|
||||
lowestEmptyLevel = level
|
||||
pendingUpdates[level] = nil
|
||||
} else {
|
||||
// This level can only be completely full or half full,
|
||||
// so choose the appropriate offset to ensure enough
|
||||
// entries remain to reach the level.
|
||||
var offset int
|
||||
if numEntries-curLevelMaxEntries >= minPrevRequired {
|
||||
offset = curLevelMaxEntries * txEntrySize
|
||||
} else {
|
||||
offset = prevLevelMaxEntries * txEntrySize
|
||||
}
|
||||
pendingUpdates[level] = curLevelData[:offset]
|
||||
curLevelData = curLevelData[offset:]
|
||||
}
|
||||
|
||||
curLevelMaxEntries = prevLevelMaxEntries
|
||||
}
|
||||
pendingUpdates[0] = curLevelData
|
||||
if len(curLevelData) == 0 {
|
||||
lowestEmptyLevel = 0
|
||||
}
|
||||
|
||||
// When the highest loaded level is empty, it's possible the level after
|
||||
// it still has data and thus that data needs to be backfilled as well.
|
||||
for len(pendingUpdates[highestLoadedLevel]) == 0 {
|
||||
// When the next level is empty too, the is no data left to
|
||||
// continue backfilling, so there is nothing left to do.
|
||||
// Otherwise, populate the pending updates map with the newly
|
||||
// loaded data and update the highest loaded level accordingly.
|
||||
level := highestLoadedLevel + 1
|
||||
curLevelKey := keyForLevel(addrKey, level)
|
||||
levelData := bucket.Get(curLevelKey[:])
|
||||
if len(levelData) == 0 {
|
||||
break
|
||||
}
|
||||
pendingUpdates[level] = levelData
|
||||
highestLoadedLevel = level
|
||||
|
||||
// At this point the highest level is not empty, but it might
|
||||
// be half full. When that is the case, move it up a level to
|
||||
// simplify the code below which backfills all lower levels that
|
||||
// are still empty. This also means the current level will be
|
||||
// empty, so the loop will perform another another iteration to
|
||||
// potentially backfill this level with data from the next one.
|
||||
curLevelMaxEntries := maxEntriesForLevel(level)
|
||||
if len(levelData)/txEntrySize != curLevelMaxEntries {
|
||||
pendingUpdates[level] = nil
|
||||
pendingUpdates[level-1] = levelData
|
||||
level--
|
||||
curLevelMaxEntries /= 2
|
||||
}
|
||||
|
||||
// Backfill all lower levels that are still empty by iteratively
|
||||
// halfing the data until the lowest empty level is filled.
|
||||
for level > lowestEmptyLevel {
|
||||
offset := (curLevelMaxEntries / 2) * txEntrySize
|
||||
pendingUpdates[level] = levelData[:offset]
|
||||
levelData = levelData[offset:]
|
||||
pendingUpdates[level-1] = levelData
|
||||
level--
|
||||
curLevelMaxEntries /= 2
|
||||
}
|
||||
|
||||
// The lowest possible empty level is now the highest loaded
|
||||
// level.
|
||||
lowestEmptyLevel = highestLoadedLevel
|
||||
}
|
||||
|
||||
// Apply the pending updates.
|
||||
return applyPending()
|
||||
}
|
||||
|
||||
// addrToKey converts known address types to an addrindex key. An error is
|
||||
// returned for unsupported types.
|
||||
func addrToKey(addr util.Address) ([addrKeySize]byte, error) {
|
||||
switch addr := addr.(type) {
|
||||
case *util.AddressPubKeyHash:
|
||||
var result [addrKeySize]byte
|
||||
result[0] = addrKeyTypePubKeyHash
|
||||
copy(result[1:], addr.Hash160()[:])
|
||||
return result, nil
|
||||
|
||||
case *util.AddressScriptHash:
|
||||
var result [addrKeySize]byte
|
||||
result[0] = addrKeyTypeScriptHash
|
||||
copy(result[1:], addr.Hash160()[:])
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return [addrKeySize]byte{}, errUnsupportedAddressType
|
||||
}
|
||||
|
||||
// AddrIndex implements a transaction by address index. That is to say, it
|
||||
// supports querying all transactions that reference a given address because
|
||||
// they are either crediting or debiting the address. The returned transactions
|
||||
// are ordered according to their order of appearance in the blockDAG. In
|
||||
// other words, first by block height and then by offset inside the block.
|
||||
//
|
||||
// In addition, support is provided for a memory-only index of unconfirmed
|
||||
// transactions such as those which are kept in the memory pool before inclusion
|
||||
// in a block.
|
||||
type AddrIndex struct {
|
||||
// The following fields are set when the instance is created and can't
|
||||
// be changed afterwards, so there is no need to protect them with a
|
||||
// separate mutex.
|
||||
db database.DB
|
||||
dagParams *dagconfig.Params
|
||||
|
||||
// The following fields are used to quickly link transactions and
|
||||
// addresses that have not been included into a block yet when an
|
||||
// address index is being maintained. The are protected by the
|
||||
// unconfirmedLock field.
|
||||
//
|
||||
// The txnsByAddr field is used to keep an index of all transactions
|
||||
// which either create an output to a given address or spend from a
|
||||
// previous output to it keyed by the address.
|
||||
//
|
||||
// The addrsByTx field is essentially the reverse and is used to
|
||||
// keep an index of all addresses which a given transaction involves.
|
||||
// This allows fairly efficient updates when transactions are removed
|
||||
// once they are included into a block.
|
||||
unconfirmedLock sync.RWMutex
|
||||
txnsByAddr map[[addrKeySize]byte]map[daghash.TxID]*util.Tx
|
||||
addrsByTx map[daghash.TxID]map[[addrKeySize]byte]struct{}
|
||||
}
|
||||
|
||||
// Ensure the AddrIndex type implements the Indexer interface.
|
||||
var _ Indexer = (*AddrIndex)(nil)
|
||||
|
||||
// Ensure the AddrIndex type implements the NeedsInputser interface.
|
||||
var _ NeedsInputser = (*AddrIndex)(nil)
|
||||
|
||||
// NeedsInputs signals that the index requires the referenced inputs in order
|
||||
// to properly create the index.
|
||||
//
|
||||
// This implements the NeedsInputser interface.
|
||||
func (idx *AddrIndex) NeedsInputs() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Init is only provided to satisfy the Indexer interface as there is nothing to
|
||||
// initialize for this index.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) Init(db database.DB, _ *blockdag.BlockDAG) error {
|
||||
idx.db = db
|
||||
return nil
|
||||
}
|
||||
|
||||
// Key returns the database key to use for the index as a byte slice.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) Key() []byte {
|
||||
return addrIndexKey
|
||||
}
|
||||
|
||||
// Name returns the human-readable name of the index.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) Name() string {
|
||||
return addrIndexName
|
||||
}
|
||||
|
||||
// Create is invoked when the indexer manager determines the index needs
|
||||
// to be created for the first time. It creates the bucket for the address
|
||||
// index.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) Create(dbTx database.Tx) error {
|
||||
_, err := dbTx.Metadata().CreateBucket(addrIndexKey)
|
||||
return err
|
||||
}
|
||||
|
||||
// writeIndexData represents the address index data to be written for one block.
|
||||
// It consists of the address mapped to an ordered list of the transactions
|
||||
// that involve the address in block. It is ordered so the transactions can be
|
||||
// stored in the order they appear in the block.
|
||||
type writeIndexData map[[addrKeySize]byte][]int
|
||||
|
||||
// indexScriptPubKey extracts all standard addresses from the passed public key
|
||||
// script and maps each of them to the associated transaction using the passed
|
||||
// map.
|
||||
func (idx *AddrIndex) indexScriptPubKey(data writeIndexData, scriptPubKey []byte, txIdx int) {
|
||||
// Nothing to index if the script is non-standard or otherwise doesn't
|
||||
// contain any addresses.
|
||||
_, addr, err := txscript.ExtractScriptPubKeyAddress(scriptPubKey,
|
||||
idx.dagParams)
|
||||
if err != nil || addr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
addrKey, err := addrToKey(addr)
|
||||
if err != nil {
|
||||
// Ignore unsupported address types.
|
||||
return
|
||||
}
|
||||
|
||||
// Avoid inserting the transaction more than once. Since the
|
||||
// transactions are indexed serially any duplicates will be
|
||||
// indexed in a row, so checking the most recent entry for the
|
||||
// address is enough to detect duplicates.
|
||||
indexedTxns := data[addrKey]
|
||||
numTxns := len(indexedTxns)
|
||||
if numTxns > 0 && indexedTxns[numTxns-1] == txIdx {
|
||||
return
|
||||
}
|
||||
indexedTxns = append(indexedTxns, txIdx)
|
||||
data[addrKey] = indexedTxns
|
||||
}
|
||||
|
||||
// indexBlock extract all of the standard addresses from all of the transactions
|
||||
// in the passed block and maps each of them to the associated transaction using
|
||||
// the passed map.
|
||||
func (idx *AddrIndex) indexBlock(data writeIndexData, block *util.Block, dag *blockdag.BlockDAG) {
|
||||
for txIdx, tx := range block.Transactions() {
|
||||
// Coinbases do not reference any inputs. Since the block is
|
||||
// required to have already gone through full validation, it has
|
||||
// already been proven on the first transaction in the block is
|
||||
// a coinbase.
|
||||
if txIdx > util.CoinbaseTransactionIndex {
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
// The UTXO should always have the input since
|
||||
// the index contract requires it, however, be
|
||||
// safe and simply ignore any missing entries.
|
||||
entry, ok := dag.GetUTXOEntry(txIn.PreviousOutpoint)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
idx.indexScriptPubKey(data, entry.ScriptPubKey(), txIdx)
|
||||
}
|
||||
}
|
||||
|
||||
for _, txOut := range tx.MsgTx().TxOut {
|
||||
idx.indexScriptPubKey(data, txOut.ScriptPubKey, txIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectBlock is invoked by the index manager when a new block has been
|
||||
// connected to the DAG. This indexer adds a mapping for each address
|
||||
// the transactions in the block involve.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *util.Block, blockID uint64, dag *blockdag.BlockDAG,
|
||||
_ blockdag.MultiBlockTxsAcceptanceData, _ blockdag.MultiBlockTxsAcceptanceData) error {
|
||||
|
||||
// The offset and length of the transactions within the serialized
|
||||
// block.
|
||||
txLocs, err := block.TxLoc()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build all of the address to transaction mappings in a local map.
|
||||
addrsToTxns := make(writeIndexData)
|
||||
idx.indexBlock(addrsToTxns, block, dag)
|
||||
|
||||
// Add all of the index entries for each address.
|
||||
addrIdxBucket := dbTx.Metadata().Bucket(addrIndexKey)
|
||||
for addrKey, txIdxs := range addrsToTxns {
|
||||
for _, txIdx := range txIdxs {
|
||||
err := dbPutAddrIndexEntry(addrIdxBucket, addrKey,
|
||||
blockID, txLocs[txIdx])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TxRegionsForAddress returns a slice of block regions which identify each
|
||||
// transaction that involves the passed address according to the specified
|
||||
// number to skip, number requested, and whether or not the results should be
|
||||
// reversed. It also returns the number actually skipped since it could be less
|
||||
// in the case where there are not enough entries.
|
||||
//
|
||||
// NOTE: These results only include transactions confirmed in blocks. See the
|
||||
// UnconfirmedTxnsForAddress method for obtaining unconfirmed transactions
|
||||
// that involve a given address.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (idx *AddrIndex) TxRegionsForAddress(dbTx database.Tx, addr util.Address, numToSkip, numRequested uint32, reverse bool) ([]database.BlockRegion, uint32, error) {
|
||||
addrKey, err := addrToKey(addr)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var regions []database.BlockRegion
|
||||
var skipped uint32
|
||||
err = idx.db.View(func(dbTx database.Tx) error {
|
||||
// Create closure to lookup the block hash given the ID using
|
||||
// the database transaction.
|
||||
fetchBlockHash := func(id []byte) (*daghash.Hash, error) {
|
||||
// Deserialize and populate the result.
|
||||
return blockdag.DBFetchBlockHashBySerializedID(dbTx, id)
|
||||
}
|
||||
|
||||
var err error
|
||||
addrIdxBucket := dbTx.Metadata().Bucket(addrIndexKey)
|
||||
regions, skipped, err = dbFetchAddrIndexEntries(addrIdxBucket,
|
||||
addrKey, numToSkip, numRequested, reverse,
|
||||
fetchBlockHash)
|
||||
return err
|
||||
})
|
||||
|
||||
return regions, skipped, err
|
||||
}
|
||||
|
||||
// indexUnconfirmedAddresses modifies the unconfirmed (memory-only) address
|
||||
// index to include mappings for the addresses encoded by the passed public key
|
||||
// script to the transaction.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (idx *AddrIndex) indexUnconfirmedAddresses(scriptPubKey []byte, tx *util.Tx) {
|
||||
// The error is ignored here since the only reason it can fail is if the
|
||||
// script fails to parse and it was already validated before being
|
||||
// admitted to the mempool.
|
||||
_, addr, _ := txscript.ExtractScriptPubKeyAddress(scriptPubKey,
|
||||
idx.dagParams)
|
||||
// Ignore unsupported address types.
|
||||
addrKey, err := addrToKey(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add a mapping from the address to the transaction.
|
||||
idx.unconfirmedLock.Lock()
|
||||
addrIndexEntry := idx.txnsByAddr[addrKey]
|
||||
if addrIndexEntry == nil {
|
||||
addrIndexEntry = make(map[daghash.TxID]*util.Tx)
|
||||
idx.txnsByAddr[addrKey] = addrIndexEntry
|
||||
}
|
||||
addrIndexEntry[*tx.ID()] = tx
|
||||
|
||||
// Add a mapping from the transaction to the address.
|
||||
addrsByTxEntry := idx.addrsByTx[*tx.ID()]
|
||||
if addrsByTxEntry == nil {
|
||||
addrsByTxEntry = make(map[[addrKeySize]byte]struct{})
|
||||
idx.addrsByTx[*tx.ID()] = addrsByTxEntry
|
||||
}
|
||||
addrsByTxEntry[addrKey] = struct{}{}
|
||||
idx.unconfirmedLock.Unlock()
|
||||
}
|
||||
|
||||
// AddUnconfirmedTx adds all addresses related to the transaction to the
|
||||
// unconfirmed (memory-only) address index.
|
||||
//
|
||||
// NOTE: This transaction MUST have already been validated by the memory pool
|
||||
// before calling this function with it and have all of the inputs available in
|
||||
// the provided utxo view. Failure to do so could result in some or all
|
||||
// addresses not being indexed.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (idx *AddrIndex) AddUnconfirmedTx(tx *util.Tx, utxoSet blockdag.UTXOSet) {
|
||||
// Index addresses of all referenced previous transaction outputs.
|
||||
//
|
||||
// The existence checks are elided since this is only called after the
|
||||
// transaction has already been validated and thus all inputs are
|
||||
// already known to exist.
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
entry, ok := utxoSet.Get(txIn.PreviousOutpoint)
|
||||
if !ok {
|
||||
// Ignore missing entries. This should never happen
|
||||
// in practice since the function comments specifically
|
||||
// call out all inputs must be available.
|
||||
continue
|
||||
}
|
||||
idx.indexUnconfirmedAddresses(entry.ScriptPubKey(), tx)
|
||||
}
|
||||
|
||||
// Index addresses of all created outputs.
|
||||
for _, txOut := range tx.MsgTx().TxOut {
|
||||
idx.indexUnconfirmedAddresses(txOut.ScriptPubKey, tx)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveUnconfirmedTx removes the passed transaction from the unconfirmed
|
||||
// (memory-only) address index.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (idx *AddrIndex) RemoveUnconfirmedTx(txID *daghash.TxID) {
|
||||
idx.unconfirmedLock.Lock()
|
||||
defer idx.unconfirmedLock.Unlock()
|
||||
|
||||
// Remove all address references to the transaction from the address
|
||||
// index and remove the entry for the address altogether if it no longer
|
||||
// references any transactions.
|
||||
for addrKey := range idx.addrsByTx[*txID] {
|
||||
delete(idx.txnsByAddr[addrKey], *txID)
|
||||
if len(idx.txnsByAddr[addrKey]) == 0 {
|
||||
delete(idx.txnsByAddr, addrKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the entry from the transaction to address lookup map as well.
|
||||
delete(idx.addrsByTx, *txID)
|
||||
}
|
||||
|
||||
// UnconfirmedTxnsForAddress returns all transactions currently in the
|
||||
// unconfirmed (memory-only) address index that involve the passed address.
|
||||
// Unsupported address types are ignored and will result in no results.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (idx *AddrIndex) UnconfirmedTxnsForAddress(addr util.Address) []*util.Tx {
|
||||
// Ignore unsupported address types.
|
||||
addrKey, err := addrToKey(addr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Protect concurrent access.
|
||||
idx.unconfirmedLock.RLock()
|
||||
defer idx.unconfirmedLock.RUnlock()
|
||||
|
||||
// Return a new slice with the results if there are any. This ensures
|
||||
// safe concurrency.
|
||||
if txns, exists := idx.txnsByAddr[addrKey]; exists {
|
||||
addressTxns := make([]*util.Tx, 0, len(txns))
|
||||
for _, tx := range txns {
|
||||
addressTxns = append(addressTxns, tx)
|
||||
}
|
||||
return addressTxns
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recover is invoked when the indexer wasn't turned on for several blocks
|
||||
// and the indexer needs to close the gaps.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) Recover(dbTx database.Tx, currentBlockID, lastKnownBlockID uint64) error {
|
||||
return errors.Errorf("addrindex was turned off for %d blocks and can't be recovered."+
|
||||
" To resume working drop the addrindex with --dropaddrindex", lastKnownBlockID-currentBlockID)
|
||||
}
|
||||
|
||||
// NewAddrIndex returns a new instance of an indexer that is used to create a
|
||||
// mapping of all addresses in the blockDAG to the respective transactions
|
||||
// that involve them.
|
||||
//
|
||||
// It implements the Indexer interface which plugs into the IndexManager that in
|
||||
// turn is used by the blockDAG package. This allows the index to be
|
||||
// seamlessly maintained along with the DAG.
|
||||
func NewAddrIndex(dagParams *dagconfig.Params) *AddrIndex {
|
||||
return &AddrIndex{
|
||||
dagParams: dagParams,
|
||||
txnsByAddr: make(map[[addrKeySize]byte]map[daghash.TxID]*util.Tx),
|
||||
addrsByTx: make(map[daghash.TxID]map[[addrKeySize]byte]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// DropAddrIndex drops the address index from the provided database if it
|
||||
// exists.
|
||||
func DropAddrIndex(db database.DB, interrupt <-chan struct{}) error {
|
||||
return dropIndex(db, addrIndexKey, addrIndexName, interrupt)
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
// 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 indexers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
// addrIndexBucket provides a mock address index database bucket by implementing
|
||||
// the internalBucket interface.
|
||||
type addrIndexBucket struct {
|
||||
levels map[[levelKeySize]byte][]byte
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of the mock address index bucket.
|
||||
func (b *addrIndexBucket) Clone() *addrIndexBucket {
|
||||
levels := make(map[[levelKeySize]byte][]byte)
|
||||
for k, v := range b.levels {
|
||||
vCopy := make([]byte, len(v))
|
||||
copy(vCopy, v)
|
||||
levels[k] = vCopy
|
||||
}
|
||||
return &addrIndexBucket{levels: levels}
|
||||
}
|
||||
|
||||
// Get returns the value associated with the key from the mock address index
|
||||
// bucket.
|
||||
//
|
||||
// This is part of the internalBucket interface.
|
||||
func (b *addrIndexBucket) Get(key []byte) []byte {
|
||||
var levelKey [levelKeySize]byte
|
||||
copy(levelKey[:], key)
|
||||
return b.levels[levelKey]
|
||||
}
|
||||
|
||||
// Put stores the provided key/value pair to the mock address index bucket.
|
||||
//
|
||||
// This is part of the internalBucket interface.
|
||||
func (b *addrIndexBucket) Put(key []byte, value []byte) error {
|
||||
var levelKey [levelKeySize]byte
|
||||
copy(levelKey[:], key)
|
||||
b.levels[levelKey] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes the provided key from the mock address index bucket.
|
||||
//
|
||||
// This is part of the internalBucket interface.
|
||||
func (b *addrIndexBucket) Delete(key []byte) error {
|
||||
var levelKey [levelKeySize]byte
|
||||
copy(levelKey[:], key)
|
||||
delete(b.levels, levelKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// printLevels returns a string with a visual representation of the provided
|
||||
// address key taking into account the max size of each level. It is useful
|
||||
// when creating and debugging test cases.
|
||||
func (b *addrIndexBucket) printLevels(addrKey [addrKeySize]byte) string {
|
||||
highestLevel := uint8(0)
|
||||
for k := range b.levels {
|
||||
if !bytes.Equal(k[:levelOffset], addrKey[:]) {
|
||||
continue
|
||||
}
|
||||
level := uint8(k[levelOffset])
|
||||
if level > highestLevel {
|
||||
highestLevel = level
|
||||
}
|
||||
}
|
||||
|
||||
var levelBuf bytes.Buffer
|
||||
_, _ = levelBuf.WriteString("\n")
|
||||
maxEntries := level0MaxEntries
|
||||
for level := uint8(0); level <= highestLevel; level++ {
|
||||
data := b.levels[keyForLevel(addrKey, level)]
|
||||
numEntries := len(data) / txEntrySize
|
||||
for i := 0; i < numEntries; i++ {
|
||||
start := i * txEntrySize
|
||||
num := byteOrder.Uint32(data[start:])
|
||||
_, _ = levelBuf.WriteString(fmt.Sprintf("%02d ", num))
|
||||
}
|
||||
for i := numEntries; i < maxEntries; i++ {
|
||||
_, _ = levelBuf.WriteString("_ ")
|
||||
}
|
||||
_, _ = levelBuf.WriteString("\n")
|
||||
maxEntries *= 2
|
||||
}
|
||||
|
||||
return levelBuf.String()
|
||||
}
|
||||
|
||||
// sanityCheck ensures that all data stored in the bucket for the given address
|
||||
// adheres to the level-based rules described by the address index
|
||||
// documentation.
|
||||
func (b *addrIndexBucket) sanityCheck(addrKey [addrKeySize]byte, expectedTotal int) error {
|
||||
// Find the highest level for the key.
|
||||
highestLevel := uint8(0)
|
||||
for k := range b.levels {
|
||||
if !bytes.Equal(k[:levelOffset], addrKey[:]) {
|
||||
continue
|
||||
}
|
||||
level := uint8(k[levelOffset])
|
||||
if level > highestLevel {
|
||||
highestLevel = level
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the expected total number of entries are present and that
|
||||
// all levels adhere to the rules described in the address index
|
||||
// documentation.
|
||||
var totalEntries int
|
||||
maxEntries := level0MaxEntries
|
||||
for level := uint8(0); level <= highestLevel; level++ {
|
||||
// Level 0 can'have more entries than the max allowed if the
|
||||
// levels after it have data and it can't be empty. All other
|
||||
// levels must either be half full or full.
|
||||
data := b.levels[keyForLevel(addrKey, level)]
|
||||
numEntries := len(data) / txEntrySize
|
||||
totalEntries += numEntries
|
||||
if level == 0 {
|
||||
if (highestLevel != 0 && numEntries == 0) ||
|
||||
numEntries > maxEntries {
|
||||
|
||||
return errors.Errorf("level %d has %d entries",
|
||||
level, numEntries)
|
||||
}
|
||||
} else if numEntries != maxEntries && numEntries != maxEntries/2 {
|
||||
return errors.Errorf("level %d has %d entries", level,
|
||||
numEntries)
|
||||
}
|
||||
maxEntries *= 2
|
||||
}
|
||||
if totalEntries != expectedTotal {
|
||||
return errors.Errorf("expected %d entries - got %d", expectedTotal,
|
||||
totalEntries)
|
||||
}
|
||||
|
||||
// Ensure all of the numbers are in order starting from the highest
|
||||
// level moving to the lowest level.
|
||||
expectedNum := uint32(0)
|
||||
for level := highestLevel + 1; level > 0; level-- {
|
||||
data := b.levels[keyForLevel(addrKey, level)]
|
||||
numEntries := len(data) / txEntrySize
|
||||
for i := 0; i < numEntries; i++ {
|
||||
start := i * txEntrySize
|
||||
num := byteOrder.Uint32(data[start:])
|
||||
if num != expectedNum {
|
||||
return errors.Errorf("level %d offset %d does "+
|
||||
"not contain the expected number of "+
|
||||
"%d - got %d", level, i, num,
|
||||
expectedNum)
|
||||
}
|
||||
expectedNum++
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestAddrIndexLevels ensures that adding and deleting entries to the address
|
||||
// index creates multiple levels as described by the address index
|
||||
// documentation.
|
||||
func TestAddrIndexLevels(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key [addrKeySize]byte
|
||||
numInsert int
|
||||
printLevels bool // Set to help debug a specific test.
|
||||
}{
|
||||
{
|
||||
name: "level 0 not full",
|
||||
numInsert: level0MaxEntries - 1,
|
||||
},
|
||||
{
|
||||
name: "level 1 half",
|
||||
numInsert: level0MaxEntries + 1,
|
||||
},
|
||||
{
|
||||
name: "level 1 full",
|
||||
numInsert: level0MaxEntries*2 + 1,
|
||||
},
|
||||
{
|
||||
name: "level 2 half, level 1 half",
|
||||
numInsert: level0MaxEntries*3 + 1,
|
||||
},
|
||||
{
|
||||
name: "level 2 half, level 1 full",
|
||||
numInsert: level0MaxEntries*4 + 1,
|
||||
},
|
||||
{
|
||||
name: "level 2 full, level 1 half",
|
||||
numInsert: level0MaxEntries*5 + 1,
|
||||
},
|
||||
{
|
||||
name: "level 2 full, level 1 full",
|
||||
numInsert: level0MaxEntries*6 + 1,
|
||||
},
|
||||
{
|
||||
name: "level 3 half, level 2 half, level 1 half",
|
||||
numInsert: level0MaxEntries*7 + 1,
|
||||
},
|
||||
{
|
||||
name: "level 3 full, level 2 half, level 1 full",
|
||||
numInsert: level0MaxEntries*12 + 1,
|
||||
},
|
||||
}
|
||||
|
||||
nextTest:
|
||||
for testNum, test := range tests {
|
||||
// Insert entries in order.
|
||||
populatedBucket := &addrIndexBucket{
|
||||
levels: make(map[[levelKeySize]byte][]byte),
|
||||
}
|
||||
for i := 0; i < test.numInsert; i++ {
|
||||
txLoc := wire.TxLoc{TxStart: i * 2}
|
||||
err := dbPutAddrIndexEntry(populatedBucket, test.key,
|
||||
uint64(i), txLoc)
|
||||
if err != nil {
|
||||
t.Errorf("dbPutAddrIndexEntry #%d (%s) - "+
|
||||
"unexpected error: %v", testNum,
|
||||
test.name, err)
|
||||
continue nextTest
|
||||
}
|
||||
}
|
||||
if test.printLevels {
|
||||
t.Log(populatedBucket.printLevels(test.key))
|
||||
}
|
||||
|
||||
// Delete entries from the populated bucket until all entries
|
||||
// have been deleted. The bucket is reset to the fully
|
||||
// populated bucket on each iteration so every combination is
|
||||
// tested. Notice the upper limit purposes exceeds the number
|
||||
// of entries to ensure attempting to delete more entries than
|
||||
// there are works correctly.
|
||||
for numDelete := 0; numDelete <= test.numInsert+1; numDelete++ {
|
||||
// Clone populated bucket to run each delete against.
|
||||
bucket := populatedBucket.Clone()
|
||||
|
||||
// Remove the number of entries for this iteration.
|
||||
err := dbRemoveAddrIndexEntries(bucket, test.key,
|
||||
numDelete)
|
||||
if err != nil {
|
||||
if numDelete <= test.numInsert {
|
||||
t.Errorf("dbRemoveAddrIndexEntries (%s) "+
|
||||
" delete %d - unexpected error: "+
|
||||
"%v", test.name, numDelete, err)
|
||||
continue nextTest
|
||||
}
|
||||
}
|
||||
if test.printLevels {
|
||||
t.Log(bucket.printLevels(test.key))
|
||||
}
|
||||
|
||||
// Sanity check the levels to ensure the adhere to all
|
||||
// rules.
|
||||
numExpected := test.numInsert
|
||||
if numDelete <= test.numInsert {
|
||||
numExpected -= numDelete
|
||||
}
|
||||
err = bucket.sanityCheck(test.key, numExpected)
|
||||
if err != nil {
|
||||
t.Errorf("sanity check fail (%s) delete %d: %v",
|
||||
test.name, numDelete, err)
|
||||
continue nextTest
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,8 +85,8 @@ func (e errDeserialize) Error() string {
|
||||
// isDeserializeErr returns whether or not the passed error is an errDeserialize
|
||||
// error.
|
||||
func isDeserializeErr(err error) bool {
|
||||
_, ok := err.(errDeserialize)
|
||||
return ok
|
||||
var deserializeErr errDeserialize
|
||||
return errors.As(err, &deserializeErr)
|
||||
}
|
||||
|
||||
// internalBucket is an abstraction over a database bucket. It is used to make
|
||||
|
||||
@@ -6,8 +6,6 @@ package indexers
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.INDX)
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
|
||||
@@ -363,6 +363,9 @@ func dropIndex(db database.DB, idxKey []byte, idxName string, interrupt <-chan s
|
||||
}
|
||||
return bucket.DeleteBucket(bucketName[len(bucketName)-1])
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the index tip, index bucket, and in-progress drop flag now
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
// 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 indexers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// txIndexName is the human-readable name for the index.
|
||||
txIndexName = "transaction index"
|
||||
|
||||
includingBlocksIndexKeyEntrySize = 8 // 4 bytes for offset + 4 bytes for transaction length
|
||||
)
|
||||
|
||||
var (
|
||||
includingBlocksIndexKey = []byte("includingblocksidx")
|
||||
|
||||
acceptingBlocksIndexKey = []byte("acceptingblocksidx")
|
||||
)
|
||||
|
||||
// txsAcceptedByVirtual is the in-memory index of txIDs that were accepted
|
||||
// by the current virtual
|
||||
var txsAcceptedByVirtual map[daghash.TxID]bool
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The transaction index consists of an entry for every transaction in the DAG.
|
||||
//
|
||||
// There are two buckets used in total. The first bucket maps the hash of
|
||||
// each transaction to its location in each block it's included in. The second bucket
|
||||
// contains all of the blocks that from their viewpoint the transaction has been
|
||||
// accepted (i.e. the transaction is found in their blue set without double spends),
|
||||
// and their blue block (or themselves) that included the transaction.
|
||||
//
|
||||
// NOTE: Although it is technically possible for multiple transactions to have
|
||||
// the same hash as long as the previous transaction with the same hash is fully
|
||||
// spent, this code only stores the most recent one because doing otherwise
|
||||
// would add a non-trivial amount of space and overhead for something that will
|
||||
// realistically never happen per the probability and even if it did, the old
|
||||
// one must be fully spent and so the most likely transaction a caller would
|
||||
// want for a given hash is the most recent one anyways.
|
||||
//
|
||||
// The including blocks index contains a sub bucket for each transaction hash (32 byte each), that its serialized format is:
|
||||
//
|
||||
// <block id> = <start offset><tx length>
|
||||
//
|
||||
// Field Type Size
|
||||
// block id uint64 8 bytes
|
||||
// start offset uint32 4 bytes
|
||||
// tx length uint32 4 bytes
|
||||
// -----
|
||||
// Total: 16 bytes
|
||||
//
|
||||
// The accepting blocks index contains a sub bucket for each transaction hash (32 byte each), that its serialized format is:
|
||||
//
|
||||
// <accepting block id> = <including block id>
|
||||
//
|
||||
// Field Type Size
|
||||
// accepting block id uint64 8 bytes
|
||||
// including block id uint64 8 bytes
|
||||
// -----
|
||||
// Total: 16 bytes
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
func putIncludingBlocksEntry(target []byte, txLoc wire.TxLoc) {
|
||||
byteOrder.PutUint32(target, uint32(txLoc.TxStart))
|
||||
byteOrder.PutUint32(target[4:], uint32(txLoc.TxLen))
|
||||
}
|
||||
|
||||
func dbPutIncludingBlocksEntry(dbTx database.Tx, txID *daghash.TxID, blockID uint64, serializedData []byte) error {
|
||||
bucket, err := dbTx.Metadata().Bucket(includingBlocksIndexKey).CreateBucketIfNotExists(txID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put(blockdag.SerializeBlockID(blockID), serializedData)
|
||||
}
|
||||
|
||||
func dbPutAcceptingBlocksEntry(dbTx database.Tx, txID *daghash.TxID, blockID uint64, serializedData []byte) error {
|
||||
bucket, err := dbTx.Metadata().Bucket(acceptingBlocksIndexKey).CreateBucketIfNotExists(txID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put(blockdag.SerializeBlockID(blockID), serializedData)
|
||||
}
|
||||
|
||||
// dbFetchFirstTxRegion uses an existing database transaction to fetch the block
|
||||
// region for the provided transaction hash from the transaction index. When
|
||||
// there is no entry for the provided hash, nil will be returned for the both
|
||||
// the region and the error.
|
||||
//
|
||||
// P.S Because the transaction can be found in multiple blocks, this function arbitarily
|
||||
// returns the first block region that is stored in the txindex.
|
||||
func dbFetchFirstTxRegion(dbTx database.Tx, txID *daghash.TxID) (*database.BlockRegion, error) {
|
||||
// Load the record from the database and return now if it doesn't exist.
|
||||
txBucket := dbTx.Metadata().Bucket(includingBlocksIndexKey).Bucket(txID[:])
|
||||
if txBucket == nil {
|
||||
return nil, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("No block region "+
|
||||
"was found for %s", txID),
|
||||
}
|
||||
}
|
||||
cursor := txBucket.Cursor()
|
||||
if ok := cursor.First(); !ok {
|
||||
return nil, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("No block region "+
|
||||
"was found for %s", txID),
|
||||
}
|
||||
}
|
||||
serializedBlockID := cursor.Key()
|
||||
serializedData := cursor.Value()
|
||||
if len(serializedData) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Ensure the serialized data has enough bytes to properly deserialize.
|
||||
if len(serializedData) < includingBlocksIndexKeyEntrySize {
|
||||
return nil, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("corrupt transaction index "+
|
||||
"entry for %s", txID),
|
||||
}
|
||||
}
|
||||
|
||||
// Load the block hash associated with the block ID.
|
||||
hash, err := blockdag.DBFetchBlockHashBySerializedID(dbTx, serializedBlockID)
|
||||
if err != nil {
|
||||
return nil, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("corrupt transaction index "+
|
||||
"entry for %s: %s", txID, err),
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize the final entry.
|
||||
region := database.BlockRegion{Hash: &daghash.Hash{}}
|
||||
copy(region.Hash[:], hash[:])
|
||||
region.Offset = byteOrder.Uint32(serializedData[:4])
|
||||
region.Len = byteOrder.Uint32(serializedData[4:])
|
||||
|
||||
return ®ion, nil
|
||||
}
|
||||
|
||||
// dbAddTxIndexEntries uses an existing database transaction to add a
|
||||
// transaction index entry for every transaction in the passed block.
|
||||
func dbAddTxIndexEntries(dbTx database.Tx, block *util.Block, blockID uint64, multiBlockTxsAcceptanceData blockdag.MultiBlockTxsAcceptanceData) error {
|
||||
// The offset and length of the transactions within the serialized
|
||||
// block.
|
||||
txLocs, err := block.TxLoc()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// As an optimization, allocate a single slice big enough to hold all
|
||||
// of the serialized transaction index entries for the block and
|
||||
// serialize them directly into the slice. Then, pass the appropriate
|
||||
// subslice to the database to be written. This approach significantly
|
||||
// cuts down on the number of required allocations.
|
||||
includingBlocksOffset := 0
|
||||
serializedIncludingBlocksValues := make([]byte, len(block.Transactions())*includingBlocksIndexKeyEntrySize)
|
||||
for i, tx := range block.Transactions() {
|
||||
putIncludingBlocksEntry(serializedIncludingBlocksValues[includingBlocksOffset:], txLocs[i])
|
||||
endOffset := includingBlocksOffset + includingBlocksIndexKeyEntrySize
|
||||
err := dbPutIncludingBlocksEntry(dbTx, tx.ID(), blockID,
|
||||
serializedIncludingBlocksValues[includingBlocksOffset:endOffset:endOffset])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
includingBlocksOffset += includingBlocksIndexKeyEntrySize
|
||||
}
|
||||
|
||||
for _, blockTxsAcceptanceData := range multiBlockTxsAcceptanceData {
|
||||
var includingBlockID uint64
|
||||
if blockTxsAcceptanceData.BlockHash.IsEqual(block.Hash()) {
|
||||
includingBlockID = blockID
|
||||
} else {
|
||||
includingBlockID, err = blockdag.DBFetchBlockIDByHash(dbTx, &blockTxsAcceptanceData.BlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
serializedIncludingBlockID := blockdag.SerializeBlockID(includingBlockID)
|
||||
|
||||
for _, txAcceptanceData := range blockTxsAcceptanceData.TxAcceptanceData {
|
||||
if !txAcceptanceData.IsAccepted {
|
||||
continue
|
||||
}
|
||||
err = dbPutAcceptingBlocksEntry(dbTx, txAcceptanceData.Tx.ID(), blockID, serializedIncludingBlockID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateTxsAcceptedByVirtual(virtualTxsAcceptanceData blockdag.MultiBlockTxsAcceptanceData) error {
|
||||
// Initialize a new txsAcceptedByVirtual
|
||||
entries := 0
|
||||
for _, blockTxsAcceptanceData := range virtualTxsAcceptanceData {
|
||||
entries += len(blockTxsAcceptanceData.TxAcceptanceData)
|
||||
}
|
||||
txsAcceptedByVirtual = make(map[daghash.TxID]bool, entries)
|
||||
|
||||
// Copy virtualTxsAcceptanceData to txsAcceptedByVirtual
|
||||
for _, blockTxsAcceptanceData := range virtualTxsAcceptanceData {
|
||||
for _, txAcceptanceData := range blockTxsAcceptanceData.TxAcceptanceData {
|
||||
txsAcceptedByVirtual[*txAcceptanceData.Tx.ID()] = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TxIndex implements a transaction by hash index. That is to say, it supports
|
||||
// querying all transactions by their hash.
|
||||
type TxIndex struct {
|
||||
db database.DB
|
||||
}
|
||||
|
||||
// Ensure the TxIndex type implements the Indexer interface.
|
||||
var _ Indexer = (*TxIndex)(nil)
|
||||
|
||||
// Init initializes the hash-based transaction index. In particular, it finds
|
||||
// the highest used block ID and stores it for later use when connecting or
|
||||
// disconnecting blocks.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *TxIndex) Init(db database.DB, dag *blockdag.BlockDAG) error {
|
||||
idx.db = db
|
||||
|
||||
// Initialize the txsAcceptedByVirtual index
|
||||
virtualTxsAcceptanceData, err := dag.TxsAcceptedByVirtual()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = updateTxsAcceptedByVirtual(virtualTxsAcceptanceData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Key returns the database key to use for the index as a byte slice.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *TxIndex) Key() []byte {
|
||||
return includingBlocksIndexKey
|
||||
}
|
||||
|
||||
// Name returns the human-readable name of the index.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *TxIndex) Name() string {
|
||||
return txIndexName
|
||||
}
|
||||
|
||||
// Create is invoked when the indexer manager determines the index needs
|
||||
// to be created for the first time. It creates the buckets for the hash-based
|
||||
// transaction index and the internal block ID indexes.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *TxIndex) Create(dbTx database.Tx) error {
|
||||
meta := dbTx.Metadata()
|
||||
if _, err := meta.CreateBucket(includingBlocksIndexKey); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := meta.CreateBucket(acceptingBlocksIndexKey)
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// ConnectBlock is invoked by the index manager when a new block has been
|
||||
// connected to the DAG. This indexer adds a hash-to-transaction mapping
|
||||
// for every transaction in the passed block.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *TxIndex) ConnectBlock(dbTx database.Tx, block *util.Block, blockID uint64, dag *blockdag.BlockDAG,
|
||||
acceptedTxsData blockdag.MultiBlockTxsAcceptanceData, virtualTxsAcceptanceData blockdag.MultiBlockTxsAcceptanceData) error {
|
||||
if err := dbAddTxIndexEntries(dbTx, block, blockID, acceptedTxsData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := updateTxsAcceptedByVirtual(virtualTxsAcceptanceData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TxFirstBlockRegion returns the first block region for the provided transaction hash
|
||||
// from the transaction index. The block region can in turn be used to load the
|
||||
// raw transaction bytes. When there is no entry for the provided hash, nil
|
||||
// will be returned for the both the entry and the error.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (idx *TxIndex) TxFirstBlockRegion(txID *daghash.TxID) (*database.BlockRegion, error) {
|
||||
var region *database.BlockRegion
|
||||
err := idx.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
region, err = dbFetchFirstTxRegion(dbTx, txID)
|
||||
return err
|
||||
})
|
||||
return region, err
|
||||
}
|
||||
|
||||
// TxBlocks returns the hashes of the blocks where the transaction exists
|
||||
func (idx *TxIndex) TxBlocks(txHash *daghash.Hash) ([]*daghash.Hash, error) {
|
||||
blockHashes := make([]*daghash.Hash, 0)
|
||||
err := idx.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
blockHashes, err = dbFetchTxBlocks(dbTx, txHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return blockHashes, err
|
||||
}
|
||||
|
||||
func dbFetchTxBlocks(dbTx database.Tx, txHash *daghash.Hash) ([]*daghash.Hash, error) {
|
||||
blockHashes := make([]*daghash.Hash, 0)
|
||||
bucket := dbTx.Metadata().Bucket(includingBlocksIndexKey).Bucket(txHash[:])
|
||||
if bucket == nil {
|
||||
return nil, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("No including blocks "+
|
||||
"were found for %s", txHash),
|
||||
}
|
||||
}
|
||||
err := bucket.ForEach(func(serializedBlockID, _ []byte) error {
|
||||
blockHash, err := blockdag.DBFetchBlockHashBySerializedID(dbTx, serializedBlockID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockHashes = append(blockHashes, blockHash)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blockHashes, nil
|
||||
}
|
||||
|
||||
// BlockThatAcceptedTx returns the hash of the block where the transaction got accepted (from the virtual block point of view)
|
||||
func (idx *TxIndex) BlockThatAcceptedTx(dag *blockdag.BlockDAG, txID *daghash.TxID) (*daghash.Hash, error) {
|
||||
var acceptingBlock *daghash.Hash
|
||||
err := idx.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
acceptingBlock, err = dbFetchTxAcceptingBlock(dbTx, txID, dag)
|
||||
return err
|
||||
})
|
||||
return acceptingBlock, err
|
||||
}
|
||||
|
||||
func dbFetchTxAcceptingBlock(dbTx database.Tx, txID *daghash.TxID, dag *blockdag.BlockDAG) (*daghash.Hash, error) {
|
||||
// If the transaction was accepted by the current virtual,
|
||||
// return the zeroHash immediately
|
||||
if _, ok := txsAcceptedByVirtual[*txID]; ok {
|
||||
return &daghash.ZeroHash, nil
|
||||
}
|
||||
|
||||
bucket := dbTx.Metadata().Bucket(acceptingBlocksIndexKey).Bucket(txID[:])
|
||||
if bucket == nil {
|
||||
return nil, nil
|
||||
}
|
||||
cursor := bucket.Cursor()
|
||||
if !cursor.First() {
|
||||
return nil, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("Accepting blocks bucket is "+
|
||||
"empty for %s", txID),
|
||||
}
|
||||
}
|
||||
for ; cursor.Key() != nil; cursor.Next() {
|
||||
blockHash, err := blockdag.DBFetchBlockHashBySerializedID(dbTx, cursor.Key())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isBlockInSelectedParentChain, err := dag.IsInSelectedParentChain(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isBlockInSelectedParentChain {
|
||||
return blockHash, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NewTxIndex returns a new instance of an indexer that is used to create a
|
||||
// mapping of the hashes of all transactions in the blockDAG to the respective
|
||||
// block, location within the block, and size of the transaction.
|
||||
//
|
||||
// It implements the Indexer interface which plugs into the IndexManager that in
|
||||
// turn is used by the blockdag package. This allows the index to be
|
||||
// seamlessly maintained along with the DAG.
|
||||
func NewTxIndex() *TxIndex {
|
||||
return &TxIndex{}
|
||||
}
|
||||
|
||||
// DropTxIndex drops the transaction index from the provided database if it
|
||||
// exists. Since the address index relies on it, the address index will also be
|
||||
// dropped when it exists.
|
||||
func DropTxIndex(db database.DB, interrupt <-chan struct{}) error {
|
||||
err := dropIndex(db, addrIndexKey, addrIndexName, interrupt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dropIndex(db, includingBlocksIndexKey, addrIndexName, interrupt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dropIndex(db, acceptingBlocksIndexKey, txIndexName, interrupt)
|
||||
}
|
||||
|
||||
// Recover is invoked when the indexer wasn't turned on for several blocks
|
||||
// and the indexer needs to close the gaps.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *TxIndex) Recover(dbTx database.Tx, currentBlockID, lastKnownBlockID uint64) error {
|
||||
return errors.Errorf("txindex was turned off for %d blocks and can't be recovered."+
|
||||
" To resume working drop the txindex with --droptxindex", lastKnownBlockID-currentBlockID)
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"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/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
func createTransaction(t *testing.T, value uint64, originTx *wire.MsgTx, outputIndex uint32) *wire.MsgTx {
|
||||
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating signature script: %s", err)
|
||||
}
|
||||
txIn := &wire.TxIn{
|
||||
PreviousOutpoint: wire.Outpoint{
|
||||
TxID: *originTx.TxID(),
|
||||
Index: outputIndex,
|
||||
},
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
SignatureScript: signatureScript,
|
||||
}
|
||||
txOut := wire.NewTxOut(value, blockdag.OpTrueScript)
|
||||
tx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
|
||||
|
||||
return tx
|
||||
}
|
||||
|
||||
func TestTxIndexConnectBlock(t *testing.T) {
|
||||
blocks := make(map[daghash.Hash]*util.Block)
|
||||
|
||||
txIndex := NewTxIndex()
|
||||
indexManager := NewManager([]Indexer{txIndex})
|
||||
|
||||
params := dagconfig.SimnetParams
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
params.K = 1
|
||||
|
||||
config := blockdag.Config{
|
||||
IndexManager: indexManager,
|
||||
DAGParams: ¶ms,
|
||||
}
|
||||
|
||||
dag, teardown, err := blockdag.DAGSetup("TestTxIndexConnectBlock", config)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTxIndexConnectBlock: Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
if teardown != nil {
|
||||
defer teardown()
|
||||
}
|
||||
|
||||
prepareAndProcessBlock := func(parentHashes []*daghash.Hash, transactions []*wire.MsgTx, blockName string) *wire.MsgBlock {
|
||||
block, err := mining.PrepareBlockForTest(dag, ¶ms, parentHashes, transactions, false)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTxIndexConnectBlock: block %v got unexpected error from PrepareBlockForTest: %v", blockName, err)
|
||||
}
|
||||
utilBlock := util.NewBlock(block)
|
||||
blocks[*block.BlockHash()] = utilBlock
|
||||
isOrphan, isDelayed, err := dag.ProcessBlock(utilBlock, blockdag.BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTxIndexConnectBlock: dag.ProcessBlock got unexpected error for block %v: %v", blockName, err)
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("TestTxIndexConnectBlock: block %s "+
|
||||
"is too far in the future", blockName)
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("TestTxIndexConnectBlock: block %v was unexpectedly orphan", blockName)
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
block1 := prepareAndProcessBlock([]*daghash.Hash{params.GenesisHash}, nil, "1")
|
||||
block2Tx := createTransaction(t, block1.Transactions[0].TxOut[0].Value, block1.Transactions[0], 0)
|
||||
block2 := prepareAndProcessBlock([]*daghash.Hash{block1.BlockHash()}, []*wire.MsgTx{block2Tx}, "2")
|
||||
block3Tx := createTransaction(t, block2.Transactions[0].TxOut[0].Value, block2.Transactions[0], 0)
|
||||
block3 := prepareAndProcessBlock([]*daghash.Hash{block2.BlockHash()}, []*wire.MsgTx{block3Tx}, "3")
|
||||
|
||||
block2TxID := block2Tx.TxID()
|
||||
block2TxNewAcceptedBlock, err := txIndex.BlockThatAcceptedTx(dag, block2TxID)
|
||||
if err != nil {
|
||||
t.Errorf("TestTxIndexConnectBlock: TxAcceptedInBlock: %v", err)
|
||||
}
|
||||
block3Hash := block3.BlockHash()
|
||||
if !block2TxNewAcceptedBlock.IsEqual(block3Hash) {
|
||||
t.Errorf("TestTxIndexConnectBlock: block2Tx should've "+
|
||||
"been accepted in block %v but instead got accepted in block %v", block3Hash, block2TxNewAcceptedBlock)
|
||||
}
|
||||
|
||||
block3TxID := block3Tx.TxID()
|
||||
block3TxNewAcceptedBlock, err := txIndex.BlockThatAcceptedTx(dag, block3TxID)
|
||||
if err != nil {
|
||||
t.Errorf("TestTxIndexConnectBlock: TxAcceptedInBlock: %v", err)
|
||||
}
|
||||
if !block3TxNewAcceptedBlock.IsEqual(&daghash.ZeroHash) {
|
||||
t.Errorf("TestTxIndexConnectBlock: block3Tx should've "+
|
||||
"been accepted by the virtual block but instead got accepted in block %v", block3TxNewAcceptedBlock)
|
||||
}
|
||||
|
||||
block3A := prepareAndProcessBlock([]*daghash.Hash{block2.BlockHash()}, []*wire.MsgTx{block3Tx}, "3A")
|
||||
block4 := prepareAndProcessBlock([]*daghash.Hash{block3.BlockHash()}, nil, "4")
|
||||
prepareAndProcessBlock([]*daghash.Hash{block3A.BlockHash(), block4.BlockHash()}, nil, "5")
|
||||
|
||||
block2TxAcceptedBlock, err := txIndex.BlockThatAcceptedTx(dag, block2TxID)
|
||||
if err != nil {
|
||||
t.Errorf("TestTxIndexConnectBlock: TxAcceptedInBlock: %v", err)
|
||||
}
|
||||
|
||||
if !block2TxAcceptedBlock.IsEqual(block3Hash) {
|
||||
t.Errorf("TestTxIndexConnectBlock: block2Tx should've "+
|
||||
"been accepted in block %v but instead got accepted in block %v", block3Hash, block2TxAcceptedBlock)
|
||||
}
|
||||
|
||||
region, err := txIndex.TxFirstBlockRegion(block3TxID)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTxIndexConnectBlock: no block region was found for block3Tx")
|
||||
}
|
||||
regionBlock, ok := blocks[*region.Hash]
|
||||
if !ok {
|
||||
t.Fatalf("TestTxIndexConnectBlock: couldn't find block with hash %v", region.Hash)
|
||||
}
|
||||
|
||||
regionBlockBytes, err := regionBlock.Bytes()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTxIndexConnectBlock: Couldn't serialize block to bytes")
|
||||
}
|
||||
block3TxInBlock := regionBlockBytes[region.Offset : region.Offset+region.Len]
|
||||
|
||||
block3TxBuf := bytes.NewBuffer(make([]byte, 0, block3Tx.SerializeSize()))
|
||||
block3Tx.KaspaEncode(block3TxBuf, 0)
|
||||
blockTxBytes := block3TxBuf.Bytes()
|
||||
|
||||
if !reflect.DeepEqual(blockTxBytes, block3TxInBlock) {
|
||||
t.Errorf("TestTxIndexConnectBlock: the block region that was in the bucket doesn't match block3Tx")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,9 +5,7 @@ import (
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -25,17 +23,6 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*wire.MsgBlock, er
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sort transactions by subnetwork ID before building Merkle tree
|
||||
sort.Slice(transactions, func(i, j int) bool {
|
||||
if transactions[i].MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDCoinbase) {
|
||||
return true
|
||||
}
|
||||
if transactions[j].MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDCoinbase) {
|
||||
return false
|
||||
}
|
||||
return subnetworkid.Less(&transactions[i].MsgTx().SubnetworkID, &transactions[j].MsgTx().SubnetworkID)
|
||||
})
|
||||
|
||||
// Create a new block ready to be solved.
|
||||
hashMerkleTree := BuildHashMerkleTreeStore(transactions)
|
||||
acceptedIDMerkleRoot, err := dag.NextAcceptedIDMerkleRootNoLock()
|
||||
|
||||
@@ -57,8 +57,8 @@ type Notification struct {
|
||||
// NotificationType for details on the types and contents of notifications.
|
||||
func (dag *BlockDAG) Subscribe(callback NotificationCallback) {
|
||||
dag.notificationsLock.Lock()
|
||||
defer dag.notificationsLock.Unlock()
|
||||
dag.notifications = append(dag.notifications, callback)
|
||||
dag.notificationsLock.Unlock()
|
||||
}
|
||||
|
||||
// sendNotification sends a notification with the passed type and data if the
|
||||
@@ -68,10 +68,10 @@ func (dag *BlockDAG) sendNotification(typ NotificationType, data interface{}) {
|
||||
// Generate and send the notification.
|
||||
n := Notification{Type: typ, Data: data}
|
||||
dag.notificationsLock.RLock()
|
||||
defer dag.notificationsLock.RUnlock()
|
||||
for _, callback := range dag.notifications {
|
||||
callback(&n)
|
||||
}
|
||||
dag.notificationsLock.RUnlock()
|
||||
}
|
||||
|
||||
// BlockAddedNotificationData defines data to be sent along with a BlockAdded
|
||||
|
||||
@@ -7,6 +7,7 @@ package blockdag
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
@@ -44,15 +45,19 @@ const (
|
||||
// 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
|
||||
)
|
||||
|
||||
// BlockExists determines whether a block with the given hash exists in
|
||||
// IsInDAG determines whether a block with the given hash exists in
|
||||
// the DAG.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) BlockExists(hash *daghash.Hash) bool {
|
||||
func (dag *BlockDAG) IsInDAG(hash *daghash.Hash) bool {
|
||||
return dag.index.HaveBlock(hash)
|
||||
}
|
||||
|
||||
@@ -95,7 +100,8 @@ func (dag *BlockDAG) processOrphans(hash *daghash.Hash, flags BehaviorFlags) err
|
||||
// still missing.
|
||||
_, err := lookupParentNodes(orphan.block, dag)
|
||||
if err != nil {
|
||||
if ruleErr, ok := err.(RuleError); ok && ruleErr.ErrorCode == ErrParentBlockUnknown {
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrParentBlockUnknown {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
@@ -111,7 +117,7 @@ func (dag *BlockDAG) processOrphans(hash *daghash.Hash, flags BehaviorFlags) err
|
||||
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 _, ok := err.(RuleError); !ok {
|
||||
if !errors.As(err, &RuleError{}) {
|
||||
return err
|
||||
}
|
||||
log.Warnf("Verification failed for orphan block %s: %s", orphanHash, err)
|
||||
@@ -144,12 +150,13 @@ func (dag *BlockDAG) ProcessBlock(block *util.Block, flags BehaviorFlags) (isOrp
|
||||
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.BlockExists(blockHash) && !wasBlockStored {
|
||||
if dag.IsInDAG(blockHash) && !wasBlockStored {
|
||||
str := fmt.Sprintf("already have block %s", blockHash)
|
||||
return false, false, ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
@@ -172,6 +179,11 @@ func (dag *BlockDAG) processBlockNoLock(block *util.Block, flags BehaviorFlags)
|
||||
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 {
|
||||
@@ -183,7 +195,7 @@ func (dag *BlockDAG) processBlockNoLock(block *util.Block, flags BehaviorFlags)
|
||||
|
||||
var missingParents []*daghash.Hash
|
||||
for _, parentHash := range block.MsgBlock().Header.ParentHashes {
|
||||
if !dag.BlockExists(parentHash) {
|
||||
if !dag.IsInDAG(parentHash) {
|
||||
missingParents = append(missingParents, parentHash)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
@@ -69,3 +71,168 @@ func TestProcessOrphans(t *testing.T) {
|
||||
t.Fatalf("TestProcessOrphans: child block erroneously not marked as invalid")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeTimeSource struct {
|
||||
time time.Time
|
||||
}
|
||||
|
||||
func (fts *fakeTimeSource) AdjustedTime() time.Time {
|
||||
return fts.time
|
||||
}
|
||||
|
||||
func (fts *fakeTimeSource) AddTimeSample(_ string, _ time.Time) {
|
||||
}
|
||||
|
||||
func (fts *fakeTimeSource) Offset() time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestProcessDelayedBlocks(t *testing.T) {
|
||||
// We use dag1 so we can build the test blocks with the proper
|
||||
// block header (UTXO commitment, acceptedIDMerkleroot, etc), and
|
||||
// then we use dag2 for the actual test.
|
||||
dag1, teardownFunc, err := DAGSetup("TestProcessDelayedBlocks1", Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
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 = &fakeTimeSource{initialTime.Add(time.Hour)}
|
||||
|
||||
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.dagParams.TimestampDeviationTolerance+5) * time.Second
|
||||
delayedBlock.Header.Timestamp = initialTime.Add(blockDelay)
|
||||
|
||||
isOrphan, isDelayed, err := dag1.ProcessBlock(util.NewBlock(delayedBlock), BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Fatalf("ProcessBlock returned unexpected error: %s\n", err)
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("ProcessBlock incorrectly returned delayedBlock " +
|
||||
"is an orphan\n")
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("ProcessBlock incorrectly returned delayedBlock " +
|
||||
"is delayed\n")
|
||||
}
|
||||
|
||||
delayedBlockChild, err := PrepareBlockForTest(dag1, []*daghash.Hash{delayedBlock.BlockHash()}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error in PrepareBlockForTest: %s", err)
|
||||
}
|
||||
|
||||
// Here the actual test begins. We add a delayed block and
|
||||
// its child and check that they are not added to the DAG,
|
||||
// and check that they're added only if we add a new block
|
||||
// after the delayed block timestamp is valid.
|
||||
dag2, teardownFunc2, err := DAGSetup("TestProcessDelayedBlocks2", Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
defer teardownFunc2()
|
||||
dag2.timeSource = &fakeTimeSource{initialTime}
|
||||
|
||||
isOrphan, isDelayed, err = dag2.ProcessBlock(util.NewBlock(delayedBlock), BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Fatalf("ProcessBlock returned unexpected error: %s\n", err)
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("ProcessBlock incorrectly returned delayedBlock " +
|
||||
"is an orphan\n")
|
||||
}
|
||||
if !isDelayed {
|
||||
t.Fatalf("ProcessBlock incorrectly returned delayedBlock " +
|
||||
"is not delayed\n")
|
||||
}
|
||||
|
||||
if dag2.IsInDAG(delayedBlock.BlockHash()) {
|
||||
t.Errorf("dag.IsInDAG should return false for a delayed block")
|
||||
}
|
||||
if !dag2.IsKnownBlock(delayedBlock.BlockHash()) {
|
||||
t.Errorf("dag.IsKnownBlock should return true for a a delayed block")
|
||||
}
|
||||
|
||||
isOrphan, isDelayed, err = dag2.ProcessBlock(util.NewBlock(delayedBlockChild), BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Fatalf("ProcessBlock returned unexpected error: %s\n", err)
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("ProcessBlock incorrectly returned delayedBlockChild " +
|
||||
"is an orphan\n")
|
||||
}
|
||||
if !isDelayed {
|
||||
t.Fatalf("ProcessBlock incorrectly returned delayedBlockChild " +
|
||||
"is not delayed\n")
|
||||
}
|
||||
|
||||
if dag2.IsInDAG(delayedBlockChild.BlockHash()) {
|
||||
t.Errorf("dag.IsInDAG should return false for a child of a delayed block")
|
||||
}
|
||||
if !dag2.IsKnownBlock(delayedBlockChild.BlockHash()) {
|
||||
t.Errorf("dag.IsKnownBlock should return true for a child of a delayed block")
|
||||
}
|
||||
|
||||
blockBeforeDelay, err := PrepareBlockForTest(dag2, []*daghash.Hash{dag2.dagParams.GenesisBlock.BlockHash()}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error in PrepareBlockForTest: %s", err)
|
||||
}
|
||||
isOrphan, isDelayed, err = dag2.ProcessBlock(util.NewBlock(blockBeforeDelay), BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Fatalf("ProcessBlock returned unexpected error: %s\n", err)
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("ProcessBlock incorrectly returned blockBeforeDelay " +
|
||||
"is an orphan\n")
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("ProcessBlock incorrectly returned blockBeforeDelay " +
|
||||
"is delayed\n")
|
||||
}
|
||||
|
||||
if dag2.IsInDAG(delayedBlock.BlockHash()) {
|
||||
t.Errorf("delayedBlock shouldn't be added to the DAG because its time hasn't reached yet")
|
||||
}
|
||||
if dag2.IsInDAG(delayedBlockChild.BlockHash()) {
|
||||
t.Errorf("delayedBlockChild shouldn't be added to the DAG because its parent is not in the DAG")
|
||||
}
|
||||
|
||||
// We advance the clock to the point where delayedBlock timestamp is valid.
|
||||
secondsUntilDelayedBlockIsValid := delayedBlock.Header.Timestamp.Unix() - int64(dag2.TimestampDeviationTolerance) - dag2.AdjustedTime().Unix() + 1
|
||||
dag2.timeSource = &fakeTimeSource{initialTime.Add(time.Duration(secondsUntilDelayedBlockIsValid) * time.Second)}
|
||||
|
||||
blockAfterDelay, err := PrepareBlockForTest(dag2, []*daghash.Hash{dag2.dagParams.GenesisBlock.BlockHash()}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error in PrepareBlockForTest: %s", err)
|
||||
}
|
||||
isOrphan, isDelayed, err = dag2.ProcessBlock(util.NewBlock(blockAfterDelay), BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Fatalf("ProcessBlock returned unexpected error: %s\n", err)
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("ProcessBlock incorrectly returned blockBeforeDelay " +
|
||||
"is an orphan\n")
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("ProcessBlock incorrectly returned blockBeforeDelay " +
|
||||
"is not delayed\n")
|
||||
}
|
||||
|
||||
if !dag2.IsInDAG(delayedBlock.BlockHash()) {
|
||||
t.Fatalf("delayedBlock should be added to the DAG because its time has been reached")
|
||||
}
|
||||
if !dag2.IsInDAG(delayedBlockChild.BlockHash()) {
|
||||
t.Errorf("delayedBlockChild shouldn't be added to the DAG because its parent has been added to the DAG")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// reachabilityInterval represents an interval to be used within the
|
||||
@@ -190,11 +191,6 @@ type reachabilityTreeNode struct {
|
||||
// remainingInterval is the not-yet allocated interval (within
|
||||
// this node's interval) awaiting new children
|
||||
remainingInterval *reachabilityInterval
|
||||
|
||||
// subtreeSize is a helper field used only during reindexing
|
||||
// (expected to be 0 any other time).
|
||||
// See countSubtrees for further details.
|
||||
subtreeSize uint64
|
||||
}
|
||||
|
||||
func newReachabilityTreeNode(blockNode *blockNode) *reachabilityTreeNode {
|
||||
@@ -218,7 +214,16 @@ func (rtn *reachabilityTreeNode) addChild(child *reachabilityTreeNode) ([]*reach
|
||||
|
||||
// No allocation space left -- reindex
|
||||
if rtn.remainingInterval.size() == 0 {
|
||||
return rtn.reindexIntervals()
|
||||
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
|
||||
@@ -253,10 +258,12 @@ func (rtn *reachabilityTreeNode) reindexIntervals() ([]*reachabilityTreeNode, er
|
||||
|
||||
// Initial interval and subtree sizes
|
||||
intervalSize := current.interval.size()
|
||||
subtreeSize := current.countSubtrees()
|
||||
subTreeSizeMap := make(map[*reachabilityTreeNode]uint64)
|
||||
current.countSubtrees(subTreeSizeMap)
|
||||
currentSubtreeSize := subTreeSizeMap[current]
|
||||
|
||||
// Find the first ancestor that has sufficient interval space
|
||||
for intervalSize < subtreeSize {
|
||||
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.
|
||||
@@ -267,14 +274,16 @@ func (rtn *reachabilityTreeNode) reindexIntervals() ([]*reachabilityTreeNode, er
|
||||
}
|
||||
current = current.parent
|
||||
intervalSize = current.interval.size()
|
||||
subtreeSize = current.countSubtrees()
|
||||
current.countSubtrees(subTreeSizeMap)
|
||||
currentSubtreeSize = subTreeSizeMap[current]
|
||||
}
|
||||
|
||||
// Propagate the interval to the subtree
|
||||
return current.propagateInterval()
|
||||
return current.propagateInterval(subTreeSizeMap)
|
||||
}
|
||||
|
||||
// countSubtrees counts the size of each subtree under this node.
|
||||
// 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 {
|
||||
@@ -293,36 +302,20 @@ func (rtn *reachabilityTreeNode) reindexIntervals() ([]*reachabilityTreeNode, er
|
||||
// intermediate updates from leaves via parent chains until all
|
||||
// size information is gathered at the root of the operation
|
||||
// (i.e. at rtn).
|
||||
//
|
||||
// Note the role of the subtreeSize field in the algorithm.
|
||||
// For each node rtn this field is initialized to 0. The field
|
||||
// has two possible states:
|
||||
// * rtn.subtreeSize > |rtn.children|:
|
||||
// indicates that rtn's subtree size is already known and
|
||||
// calculated.
|
||||
// * rtn.subtreeSize <= |rtn.children|:
|
||||
// indicates that we are still in the counting stage of
|
||||
// tracking which of rtn's children has already calculated
|
||||
// its subtree size.
|
||||
// This way, once rtn.subtree_size = |rtn.children| we know we
|
||||
// can pull subtree sizes from children and continue pushing
|
||||
// the readiness signal further up.
|
||||
func (rtn *reachabilityTreeNode) countSubtrees() uint64 {
|
||||
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
|
||||
current.subtreeSize = 1
|
||||
}
|
||||
if current.subtreeSize <= uint64(len(current.children)) {
|
||||
subTreeSizeMap[current] = 1
|
||||
} else if calculatedChildrenCount[current] <= uint64(len(current.children)) {
|
||||
// We haven't yet calculated the subtree size of
|
||||
// the current node. Add all its children to the
|
||||
// queue
|
||||
for _, child := range current.children {
|
||||
queue = append(queue, child)
|
||||
}
|
||||
queue = append(queue, current.children...)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -330,39 +323,39 @@ func (rtn *reachabilityTreeNode) countSubtrees() uint64 {
|
||||
// Push information up
|
||||
for current != rtn {
|
||||
current = current.parent
|
||||
current.subtreeSize++
|
||||
if current.subtreeSize != uint64(len(current.children)) {
|
||||
calculatedChildrenCount[current]++
|
||||
if calculatedChildrenCount[current] != uint64(len(current.children)) {
|
||||
// Not all subtrees of the current node are ready
|
||||
break
|
||||
}
|
||||
// All subtrees of current have reported readiness.
|
||||
// Count actual subtree size and continue pushing up.
|
||||
// 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 += child.subtreeSize
|
||||
childSubtreeSizeSum += subTreeSizeMap[child]
|
||||
}
|
||||
current.subtreeSize = childSubtreeSizeSum + 1
|
||||
subTreeSizeMap[current] = childSubtreeSizeSum + 1
|
||||
}
|
||||
}
|
||||
return rtn.subtreeSize
|
||||
}
|
||||
|
||||
// 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() ([]*reachabilityTreeNode, error) {
|
||||
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)
|
||||
modifiedNodes := []*reachabilityTreeNode{rtn}
|
||||
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] = child.subtreeSize
|
||||
sizes[i] = subTreeSizeMap[child]
|
||||
}
|
||||
intervals, err := current.remainingInterval.splitWithExponentialBias(sizes)
|
||||
if err != nil {
|
||||
@@ -379,9 +372,6 @@ func (rtn *reachabilityTreeNode) propagateInterval() ([]*reachabilityTreeNode, e
|
||||
}
|
||||
|
||||
modifiedNodes = append(modifiedNodes, current)
|
||||
|
||||
// Cleanup temp info for future reindexing
|
||||
current.subtreeSize = 0
|
||||
}
|
||||
return modifiedNodes, nil
|
||||
}
|
||||
@@ -401,7 +391,7 @@ func (rtn *reachabilityTreeNode) String() string {
|
||||
var current *reachabilityTreeNode
|
||||
current, queue = queue[0], queue[1:]
|
||||
if len(current.children) == 0 {
|
||||
break
|
||||
continue
|
||||
}
|
||||
|
||||
line := ""
|
||||
@@ -527,7 +517,8 @@ func (dag *BlockDAG) updateReachability(node *blockNode, selectedParentAnticone
|
||||
|
||||
// If this is the genesis node, simply initialize it and return
|
||||
if node.isGenesis() {
|
||||
return dag.reachabilityStore.setTreeNode(newTreeNode)
|
||||
dag.reachabilityStore.setTreeNode(newTreeNode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert the node into the selected parent's reachability tree
|
||||
@@ -540,10 +531,7 @@ func (dag *BlockDAG) updateReachability(node *blockNode, selectedParentAnticone
|
||||
return err
|
||||
}
|
||||
for _, modifiedTreeNode := range modifiedTreeNodes {
|
||||
err = dag.reachabilityStore.setTreeNode(modifiedTreeNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dag.reachabilityStore.setTreeNode(modifiedTreeNode)
|
||||
}
|
||||
|
||||
// Add the block to the futureCoveringSets of all the blocks
|
||||
|
||||
@@ -2,6 +2,7 @@ package blockdag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -322,6 +323,15 @@ func TestSplitWithExponentialBias(t *testing.T) {
|
||||
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 {
|
||||
@@ -473,3 +483,166 @@ func TestInsertBlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func newReachabilityStore(dag *BlockDAG) *reachabilityStore {
|
||||
}
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) setTreeNode(treeNode *reachabilityTreeNode) error {
|
||||
func (store *reachabilityStore) setTreeNode(treeNode *reachabilityTreeNode) {
|
||||
// load the reachability data from DB to store.loaded
|
||||
node := treeNode.blockNode
|
||||
_, exists := store.reachabilityDataByHash(node.hash)
|
||||
@@ -38,7 +38,6 @@ func (store *reachabilityStore) setTreeNode(treeNode *reachabilityTreeNode) erro
|
||||
|
||||
store.loaded[*node.hash].treeNode = treeNode
|
||||
store.setBlockAsDirty(node.hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) setFutureCoveringSet(node *blockNode, futureCoveringSet futureCoveringBlockSet) error {
|
||||
@@ -89,6 +88,7 @@ func (store *reachabilityStore) flushToDB(dbTx database.Tx) error {
|
||||
}
|
||||
|
||||
for hash := range store.dirty {
|
||||
hash := hash // Copy hash to a new variable to avoid passing the same pointer
|
||||
reachabilityData := store.loaded[hash]
|
||||
err := store.dbStoreReachabilityData(dbTx, &hash, reachabilityData)
|
||||
if err != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -26,9 +27,6 @@ const (
|
||||
// testDbType is the database backend type to use for the tests.
|
||||
testDbType = "ffldb"
|
||||
|
||||
// testDbRoot is the root directory used to create all test databases.
|
||||
testDbRoot = "testdbs"
|
||||
|
||||
// blockDataNet is the expected network in the test block data.
|
||||
blockDataNet = wire.Mainnet
|
||||
)
|
||||
@@ -79,16 +77,9 @@ func DAGSetup(dbName string, config Config) (*BlockDAG, func(), error) {
|
||||
}
|
||||
|
||||
if config.DB == nil {
|
||||
// Create the root directory for test databases.
|
||||
if !FileExists(testDbRoot) {
|
||||
if err := os.MkdirAll(testDbRoot, 0700); err != nil {
|
||||
err := errors.Errorf("unable to create test db "+
|
||||
"root: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
tmpDir := os.TempDir()
|
||||
|
||||
dbPath := filepath.Join(testDbRoot, dbName)
|
||||
dbPath := filepath.Join(tmpDir, dbName)
|
||||
_ = os.RemoveAll(dbPath)
|
||||
var err error
|
||||
config.DB, err = database.Create(testDbType, dbPath, blockDataNet)
|
||||
@@ -103,7 +94,6 @@ func DAGSetup(dbName string, config Config) (*BlockDAG, func(), error) {
|
||||
spawn = realSpawn
|
||||
config.DB.Close()
|
||||
os.RemoveAll(dbPath)
|
||||
os.RemoveAll(testDbRoot)
|
||||
}
|
||||
} else {
|
||||
teardown = func() {
|
||||
@@ -173,7 +163,7 @@ func SetVirtualForTest(dag *BlockDAG, virtual VirtualForTest) VirtualForTest {
|
||||
|
||||
// GetVirtualFromParentsForTest generates a virtual block with the given parents.
|
||||
func GetVirtualFromParentsForTest(dag *BlockDAG, parentHashes []*daghash.Hash) (VirtualForTest, error) {
|
||||
parents := newSet()
|
||||
parents := newBlockSet()
|
||||
for _, hash := range parentHashes {
|
||||
parent := dag.index.LookupNode(hash)
|
||||
if parent == nil {
|
||||
@@ -288,6 +278,17 @@ func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactio
|
||||
blockTransactions[i+1] = util.NewTx(tx)
|
||||
}
|
||||
|
||||
// Sort transactions by subnetwork ID
|
||||
sort.Slice(blockTransactions, func(i, j int) bool {
|
||||
if blockTransactions[i].MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDCoinbase) {
|
||||
return true
|
||||
}
|
||||
if blockTransactions[j].MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDCoinbase) {
|
||||
return false
|
||||
}
|
||||
return subnetworkid.Less(&blockTransactions[i].MsgTx().SubnetworkID, &blockTransactions[j].MsgTx().SubnetworkID)
|
||||
})
|
||||
|
||||
block, err := dag.BlockForMining(blockTransactions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -230,9 +230,6 @@ func (dag *BlockDAG) thresholdState(prevNode *blockNode, checker thresholdCondit
|
||||
if condition {
|
||||
count++
|
||||
}
|
||||
|
||||
// Get the previous block node.
|
||||
current = current.selectedParent
|
||||
}
|
||||
|
||||
// The state is locked in if the number of blocks in the
|
||||
|
||||
@@ -166,6 +166,7 @@ func (diffStore *utxoDiffStore) flushToDB(dbTx database.Tx) error {
|
||||
// while writing each entry.
|
||||
buffer := &bytes.Buffer{}
|
||||
for hash := range diffStore.dirty {
|
||||
hash := hash // Copy hash to a new variable to avoid passing the same pointer
|
||||
buffer.Reset()
|
||||
diffData := diffStore.loaded[hash]
|
||||
err := dbStoreDiffData(dbTx, buffer, &hash, diffData)
|
||||
|
||||
@@ -2,11 +2,12 @@ package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
@@ -224,6 +225,10 @@ func (d *UTXODiff) diffFrom(other *UTXODiff) (*UTXODiff, error) {
|
||||
for outpoint, utxoEntry := range d.toAdd {
|
||||
if !other.toAdd.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore) {
|
||||
result.toRemove.add(outpoint, utxoEntry)
|
||||
} else if (d.toRemove.contains(outpoint) && !other.toRemove.contains(outpoint)) ||
|
||||
(!d.toRemove.contains(outpoint) && other.toRemove.contains(outpoint)) {
|
||||
return nil, errors.New(
|
||||
"diffFrom: outpoint both in d.toAdd, other.toAdd, and only one of d.toRemove and other.toRemove")
|
||||
}
|
||||
if diffEntry, ok := other.toRemove.get(outpoint); ok {
|
||||
// An exception is made for entries with unequal blue scores
|
||||
@@ -243,6 +248,18 @@ func (d *UTXODiff) diffFrom(other *UTXODiff) (*UTXODiff, error) {
|
||||
// If they are not in other.toRemove - should be added in result.toAdd
|
||||
// If they are in other.toAdd - base utxoSet is not the same
|
||||
for outpoint, utxoEntry := range d.toRemove {
|
||||
diffEntry, ok := other.toRemove.get(outpoint)
|
||||
if ok {
|
||||
// if have the same entry in d.toRemove - simply don't copy.
|
||||
// unless existing entry is with different blue score, in this case - this is an error
|
||||
if utxoEntry.blockBlueScore != diffEntry.blockBlueScore {
|
||||
return nil, errors.New("diffFrom: outpoint both in d.toRemove and other.toRemove with different " +
|
||||
"blue scores, with no corresponding entry in d.toAdd")
|
||||
}
|
||||
} else { // if no existing entry - add to result.toAdd
|
||||
result.toAdd.add(outpoint, utxoEntry)
|
||||
}
|
||||
|
||||
if !other.toRemove.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore) {
|
||||
result.toAdd.add(outpoint, utxoEntry)
|
||||
}
|
||||
@@ -256,7 +273,7 @@ func (d *UTXODiff) diffFrom(other *UTXODiff) (*UTXODiff, error) {
|
||||
other.toRemove.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore)) {
|
||||
continue
|
||||
}
|
||||
return nil, errors.New("diffFrom: transaction both in d.toRemove and in other.toAdd")
|
||||
return nil, errors.New("diffFrom: outpoint both in d.toRemove and in other.toAdd")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,6 +301,56 @@ func (d *UTXODiff) diffFrom(other *UTXODiff) (*UTXODiff, error) {
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// WithDiffInPlace applies provided diff to this diff in-place, that would be the result if
|
||||
// first d, and than diff were applied to the same base
|
||||
func (d *UTXODiff) WithDiffInPlace(diff *UTXODiff) error {
|
||||
for outpoint, entryToRemove := range diff.toRemove {
|
||||
if d.toAdd.containsWithBlueScore(outpoint, entryToRemove.blockBlueScore) {
|
||||
// If already exists in toAdd with the same blueScore - remove from toAdd
|
||||
d.toAdd.remove(outpoint)
|
||||
continue
|
||||
}
|
||||
if d.toRemove.contains(outpoint) {
|
||||
// If already exists - this is an error
|
||||
return ruleError(ErrWithDiff, fmt.Sprintf(
|
||||
"WithDiffInPlace: outpoint %s both in d.toRemove and in diff.toRemove", outpoint))
|
||||
}
|
||||
|
||||
// If not exists neither in toAdd nor in toRemove - add to toRemove
|
||||
d.toRemove.add(outpoint, entryToRemove)
|
||||
}
|
||||
|
||||
for outpoint, entryToAdd := range diff.toAdd {
|
||||
if d.toRemove.containsWithBlueScore(outpoint, entryToAdd.blockBlueScore) {
|
||||
// If already exists in toRemove with the same blueScore - remove from toRemove
|
||||
if d.toAdd.contains(outpoint) && !diff.toRemove.contains(outpoint) {
|
||||
return ruleError(ErrWithDiff, fmt.Sprintf(
|
||||
"WithDiffInPlace: outpoint %s both in d.toAdd and in diff.toAdd with no "+
|
||||
"corresponding entry in diff.toRemove", outpoint))
|
||||
}
|
||||
d.toRemove.remove(outpoint)
|
||||
continue
|
||||
}
|
||||
if existingEntry, ok := d.toAdd.get(outpoint); ok &&
|
||||
(existingEntry.blockBlueScore == entryToAdd.blockBlueScore ||
|
||||
!diff.toRemove.containsWithBlueScore(outpoint, existingEntry.blockBlueScore)) {
|
||||
// If already exists - this is an error
|
||||
return ruleError(ErrWithDiff, fmt.Sprintf(
|
||||
"WithDiffInPlace: outpoint %s both in d.toAdd and in diff.toAdd", outpoint))
|
||||
}
|
||||
|
||||
// If not exists neither in toAdd nor in toRemove, or exists in toRemove with different blueScore - add to toAdd
|
||||
d.toAdd.add(outpoint, entryToAdd)
|
||||
}
|
||||
|
||||
// Apply diff.diffMultiset to d.diffMultiset
|
||||
if d.useMultiset {
|
||||
d.diffMultiset = d.diffMultiset.Union(diff.diffMultiset)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithDiff applies provided diff to this diff, creating a new utxoDiff, that would be the result if
|
||||
// first d, and than diff were applied to the same base
|
||||
//
|
||||
@@ -331,8 +398,7 @@ func (d *UTXODiff) WithDiff(diff *UTXODiff) (*UTXODiff, error) {
|
||||
// or diff.toRemove.
|
||||
// These are just "updates" to accepted blue score
|
||||
if diffEntry.blockBlueScore != utxoEntry.blockBlueScore &&
|
||||
(d.toRemove.containsWithBlueScore(outpoint, diffEntry.blockBlueScore) ||
|
||||
diff.toRemove.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore)) {
|
||||
diff.toRemove.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore) {
|
||||
continue
|
||||
}
|
||||
return nil, ruleError(ErrWithDiff, fmt.Sprintf("WithDiff: outpoint %s both in d.toAdd and in other.toAdd", outpoint))
|
||||
@@ -353,11 +419,10 @@ func (d *UTXODiff) WithDiff(diff *UTXODiff) (*UTXODiff, error) {
|
||||
// or diff.toAdd.
|
||||
// These are just "updates" to accepted blue score
|
||||
if diffEntry.blockBlueScore != utxoEntry.blockBlueScore &&
|
||||
(d.toAdd.containsWithBlueScore(outpoint, diffEntry.blockBlueScore) ||
|
||||
diff.toAdd.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore)) {
|
||||
d.toAdd.containsWithBlueScore(outpoint, diffEntry.blockBlueScore) {
|
||||
continue
|
||||
}
|
||||
return nil, ruleError(ErrWithDiff, "WithDiff: transaction both in d.toRemove and in other.toRemove")
|
||||
return nil, ruleError(ErrWithDiff, "WithDiff: outpoint both in d.toRemove and in other.toRemove")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
@@ -136,8 +137,11 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
// For each of the following test cases, we will:
|
||||
// this.diffFrom(other) and compare it to expectedDiffFromResult
|
||||
// this.WithDiff(other) and compare it to expectedWithDiffResult
|
||||
// this.WithDiffInPlace(other) and compare it to expectedWithDiffResult
|
||||
//
|
||||
// Note: an expected nil result means that we expect the respective operation to fail
|
||||
// See the following spreadsheet for a summary of all test-cases:
|
||||
// https://docs.google.com/spreadsheets/d/1E8G3mp5y1-yifouwLLXRLueSRfXdDRwRKFieYE07buY/edit?usp=sharing
|
||||
tests := []struct {
|
||||
name string
|
||||
this *UTXODiff
|
||||
@@ -146,7 +150,7 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
expectedWithDiffResult *UTXODiff
|
||||
}{
|
||||
{
|
||||
name: "one toAdd in this, one toAdd in other",
|
||||
name: "first toAdd in this, first toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
@@ -162,7 +166,23 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "one toAdd in this, one toRemove in other",
|
||||
name: "first in toAdd in this, second in toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "first in toAdd in this, second in toRemove in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
@@ -178,7 +198,36 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one toAdd in this, empty other",
|
||||
name: "first in toAdd in this and other, second in toRemove in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "first in toAdd in this and toRemove in other, second in toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "first in toAdd in this, empty other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
@@ -197,7 +246,7 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one toRemove in this, one toAdd in other",
|
||||
name: "first in toRemove in this and in toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
@@ -213,7 +262,23 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one toRemove in this, one toRemove in other",
|
||||
name: "first in toRemove in this, second in toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "first in toRemove in this and other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
@@ -229,7 +294,49 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "one toRemove in this, empty other",
|
||||
name: "first in toRemove in this, second in toRemove in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "first in toRemove in this and toAdd in other, second in toRemove in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "first in toRemove in this and other, second in toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "first in toRemove in this, empty other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
@@ -248,7 +355,116 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty this, one toAdd in other",
|
||||
name: "first in toAdd in this and other, second in toRemove in this",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "first in toAdd in this, second in toRemove in this and toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "first in toAdd in this and toRemove in other, second in toRemove in this",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "first in toAdd in this, second in toRemove in this and in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "first in toAdd and second in toRemove in both this and other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "first in toAdd in this and toRemove in other, second in toRemove in this and toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "first in toAdd and second in toRemove in this, empty other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedWithDiffResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty this, first in toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
@@ -267,7 +483,7 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty this, one toRemove in other",
|
||||
name: "empty this, first in toRemove in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
@@ -285,6 +501,25 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty this, first in toAdd and second in toRemove in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedWithDiffResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty this, empty other",
|
||||
this: &UTXODiff{
|
||||
@@ -304,108 +539,6 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "equal outpoints different blue scores: first in toAdd in this, second in toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "equal outpoints different blue scores: first in toRemove in this, second in toRemove in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "equal outpoints different blue scores: first in toAdd and second in toRemove in this, empty other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedWithDiffResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "equal outpoints different blue scores: empty this, first in toAdd and second in toRemove in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedWithDiffResult: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "equal outpoints different blue scores: first in toAdd and second in toRemove in both this and other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
expectedDiffFromResult: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
expectedWithDiffResult: nil,
|
||||
},
|
||||
{
|
||||
name: "equal outpoints different blue scores: first in toAdd in this and toRemove in other, second in toRemove in this and toAdd in other",
|
||||
this: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry1},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry2},
|
||||
},
|
||||
other: &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry2},
|
||||
toRemove: utxoCollection{outpoint0: utxoEntry1},
|
||||
},
|
||||
expectedDiffFromResult: nil,
|
||||
expectedWithDiffResult: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -460,6 +593,24 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, expectedWithDiffResult, withDiffResult)
|
||||
}
|
||||
|
||||
// Repeat WithDiff check this time using WithDiffInPlace
|
||||
thisClone := this.clone()
|
||||
err = thisClone.WithDiffInPlace(other)
|
||||
|
||||
// Test whether WithDiffInPlace returned an error
|
||||
isWithDiffInPlaceOk := err == nil
|
||||
expectedIsWithDiffInPlaceOk := expectedWithDiffResult != nil
|
||||
if isWithDiffInPlaceOk != expectedIsWithDiffInPlaceOk {
|
||||
t.Errorf("unexpected WithDiffInPlace error in test \"%s\". "+
|
||||
"Expected: \"%t\", got: \"%t\".", test.name, expectedIsWithDiffInPlaceOk, isWithDiffInPlaceOk)
|
||||
}
|
||||
|
||||
// If not error, test the WithDiffInPlace result
|
||||
if isWithDiffInPlaceOk && !thisClone.equal(expectedWithDiffResult) {
|
||||
t.Errorf("unexpected WithDiffInPlace result in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, expectedWithDiffResult, thisClone)
|
||||
}
|
||||
|
||||
// Make sure that diffFrom after WithDiff results in the original other
|
||||
if isWithDiffOk {
|
||||
otherResult, err := this.diffFrom(withDiffResult)
|
||||
|
||||
@@ -446,9 +446,9 @@ func (dag *BlockDAG) checkBlockHeaderSanity(header *wire.BlockHeader, flags Beha
|
||||
|
||||
//checkBlockParentsOrder ensures that the block's parents are ordered by hash
|
||||
func checkBlockParentsOrder(header *wire.BlockHeader) error {
|
||||
sortedHashes := make([]*daghash.Hash, 0, header.NumParentBlocks())
|
||||
for _, hash := range header.ParentHashes {
|
||||
sortedHashes = append(sortedHashes, hash)
|
||||
sortedHashes := make([]*daghash.Hash, header.NumParentBlocks())
|
||||
for i, hash := range header.ParentHashes {
|
||||
sortedHashes[i] = hash
|
||||
}
|
||||
sort.Slice(sortedHashes, func(i, j int) bool {
|
||||
return daghash.Less(sortedHashes[i], sortedHashes[j])
|
||||
@@ -598,41 +598,32 @@ func (dag *BlockDAG) validateDifficulty(header *wire.BlockHeader, bluestParent *
|
||||
}
|
||||
|
||||
// validateParents validates that no parent is an ancestor of another parent, and no parent is finalized
|
||||
func validateParents(blockHeader *wire.BlockHeader, parents blockSet) error {
|
||||
minBlueScore := uint64(math.MaxUint64)
|
||||
queue := newDownHeap()
|
||||
visited := newSet()
|
||||
for parent := range parents {
|
||||
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.checkFinalityRules.
|
||||
if parent.isFinalized {
|
||||
return ruleError(ErrFinality, fmt.Sprintf("block %s is a finalized parent of block %s", parent.hash, blockHeader.BlockHash()))
|
||||
if parentA.isFinalized {
|
||||
return ruleError(ErrFinality, fmt.Sprintf("block %s is a finalized "+
|
||||
"parent of block %s", parentA.hash, blockHeader.BlockHash()))
|
||||
}
|
||||
if parent.blueScore < minBlueScore {
|
||||
minBlueScore = parent.blueScore
|
||||
}
|
||||
for grandParent := range parent.parents {
|
||||
if !visited.contains(grandParent) {
|
||||
queue.Push(grandParent)
|
||||
visited.add(grandParent)
|
||||
|
||||
for parentB := range parents {
|
||||
if parentA == parentB {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
for queue.Len() > 0 {
|
||||
current := queue.pop()
|
||||
if parents.contains(current) {
|
||||
return ruleError(ErrInvalidParentsRelation, fmt.Sprintf("block %s is both a parent of %s and an"+
|
||||
" ancestor of another parent",
|
||||
current.hash,
|
||||
blockHeader.BlockHash()))
|
||||
}
|
||||
if current.blueScore > minBlueScore {
|
||||
for parent := range current.parents {
|
||||
if !visited.contains(parent) {
|
||||
queue.Push(parent)
|
||||
visited.add(parent)
|
||||
}
|
||||
|
||||
isAncestorOf, err := dag.isAncestorOf(parentA, parentB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isAncestorOf {
|
||||
return ruleError(ErrInvalidParentsRelation, fmt.Sprintf("block %s is both a parent of %s and an"+
|
||||
" ancestor of another parent %s",
|
||||
parentA.hash,
|
||||
blockHeader.BlockHash(),
|
||||
parentB.hash,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -654,7 +645,7 @@ func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, flag
|
||||
bluestParent := parents.bluest()
|
||||
fastAdd := flags&BFFastAdd == BFFastAdd
|
||||
|
||||
err := validateParents(&block.MsgBlock().Header, parents)
|
||||
err := dag.validateParents(&block.MsgBlock().Header, parents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -186,8 +187,8 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Errorf("CheckBlockSanity: transactions disorder is not detected")
|
||||
}
|
||||
ruleErr, ok := err.(RuleError)
|
||||
if !ok {
|
||||
var ruleErr RuleError
|
||||
if !errors.As(err, &ruleErr) {
|
||||
t.Errorf("CheckBlockSanity: wrong error returned, expect RuleError, got %T", err)
|
||||
} else if ruleErr.ErrorCode != ErrTransactionsNotSorted {
|
||||
t.Errorf("CheckBlockSanity: wrong error returned, expect ErrTransactionsNotSorted, got %v, err %s", ruleErr.ErrorCode, err)
|
||||
@@ -479,8 +480,10 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Errorf("CheckBlockSanity: error is nil when it shouldn't be")
|
||||
}
|
||||
rError := err.(RuleError)
|
||||
if rError.ErrorCode != ErrWrongParentsOrder {
|
||||
var rError RuleError
|
||||
if !errors.As(err, &rError) {
|
||||
t.Fatalf("CheckBlockSanity: expected a RuleError, but got %s", err)
|
||||
} else if rError.ErrorCode != ErrWrongParentsOrder {
|
||||
t.Errorf("CheckBlockSanity: Expected error was ErrWrongParentsOrder but got %v", err)
|
||||
}
|
||||
if delay != 0 {
|
||||
@@ -509,14 +512,14 @@ func TestPastMedianTime(t *testing.T) {
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
blockTime = blockTime.Add(time.Second)
|
||||
tip = newTestNode(dag, setFromSlice(tip),
|
||||
tip = newTestNode(dag, blockSetFromSlice(tip),
|
||||
blockVersion,
|
||||
0,
|
||||
blockTime)
|
||||
}
|
||||
|
||||
// Checks that a block is valid if it has timestamp equals to past median time
|
||||
node := newTestNode(dag, setFromSlice(tip),
|
||||
node := newTestNode(dag, blockSetFromSlice(tip),
|
||||
blockVersion,
|
||||
dag.powMaxBits,
|
||||
tip.PastMedianTime(dag))
|
||||
@@ -529,7 +532,7 @@ func TestPastMedianTime(t *testing.T) {
|
||||
}
|
||||
|
||||
// Checks that a block is valid if its timestamp is after past median time
|
||||
node = newTestNode(dag, setFromSlice(tip),
|
||||
node = newTestNode(dag, blockSetFromSlice(tip),
|
||||
blockVersion,
|
||||
dag.powMaxBits,
|
||||
tip.PastMedianTime(dag).Add(time.Second))
|
||||
@@ -542,7 +545,7 @@ func TestPastMedianTime(t *testing.T) {
|
||||
}
|
||||
|
||||
// Checks that a block is invalid if its timestamp is before past median time
|
||||
node = newTestNode(dag, setFromSlice(tip),
|
||||
node = newTestNode(dag, blockSetFromSlice(tip),
|
||||
blockVersion,
|
||||
0,
|
||||
tip.PastMedianTime(dag).Add(-time.Second))
|
||||
@@ -555,24 +558,23 @@ func TestPastMedianTime(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateParents(t *testing.T) {
|
||||
dag := newTestDAG(&dagconfig.SimnetParams)
|
||||
genesisNode := dag.genesis
|
||||
blockVersion := int32(0x10000000)
|
||||
|
||||
blockTime := genesisNode.Header().Timestamp
|
||||
|
||||
generateNode := func(parents ...*blockNode) *blockNode {
|
||||
// The timestamp of each block is changed to prevent a situation where two blocks share the same hash
|
||||
blockTime = blockTime.Add(time.Second)
|
||||
return newTestNode(dag, setFromSlice(parents...),
|
||||
blockVersion,
|
||||
0,
|
||||
blockTime)
|
||||
// Create a new database and dag instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestCheckBlockSanity", Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup dag instance: %v", err)
|
||||
return
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
a := generateNode(genesisNode)
|
||||
b := generateNode(a)
|
||||
c := generateNode(genesisNode)
|
||||
a := prepareAndProcessBlock(t, dag, dag.dagParams.GenesisBlock)
|
||||
b := prepareAndProcessBlock(t, dag, a)
|
||||
c := prepareAndProcessBlock(t, dag, dag.dagParams.GenesisBlock)
|
||||
|
||||
aNode := nodeByMsgBlock(t, dag, a)
|
||||
bNode := nodeByMsgBlock(t, dag, b)
|
||||
cNode := nodeByMsgBlock(t, dag, c)
|
||||
|
||||
fakeBlockHeader := &wire.BlockHeader{
|
||||
HashMerkleRoot: &daghash.ZeroHash,
|
||||
@@ -581,19 +583,19 @@ func TestValidateParents(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check direct parents relation
|
||||
err := validateParents(fakeBlockHeader, setFromSlice(a, b))
|
||||
err = dag.validateParents(fakeBlockHeader, blockSetFromSlice(aNode, bNode))
|
||||
if err == nil {
|
||||
t.Errorf("validateParents: `a` is a parent of `b`, so an error is expected")
|
||||
}
|
||||
|
||||
// Check indirect parents relation
|
||||
err = validateParents(fakeBlockHeader, setFromSlice(genesisNode, b))
|
||||
err = dag.validateParents(fakeBlockHeader, blockSetFromSlice(dag.genesis, bNode))
|
||||
if err == nil {
|
||||
t.Errorf("validateParents: `genesis` and `b` are indirectly related, so an error is expected")
|
||||
}
|
||||
|
||||
// Check parents with no relation
|
||||
err = validateParents(fakeBlockHeader, setFromSlice(b, c))
|
||||
err = dag.validateParents(fakeBlockHeader, blockSetFromSlice(bNode, cNode))
|
||||
if err != nil {
|
||||
t.Errorf("validateParents: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
@@ -32,22 +32,13 @@ func newVirtualBlock(dag *BlockDAG, tips blockSet) *virtualBlock {
|
||||
var virtual virtualBlock
|
||||
virtual.dag = dag
|
||||
virtual.utxoSet = NewFullUTXOSet()
|
||||
virtual.selectedParentChainSet = newSet()
|
||||
virtual.selectedParentChainSet = newBlockSet()
|
||||
virtual.selectedParentChainSlice = nil
|
||||
virtual.setTips(tips)
|
||||
|
||||
return &virtual
|
||||
}
|
||||
|
||||
// clone creates and returns a clone of the virtual block.
|
||||
func (v *virtualBlock) clone() *virtualBlock {
|
||||
return &virtualBlock{
|
||||
utxoSet: v.utxoSet,
|
||||
blockNode: v.blockNode,
|
||||
selectedParentChainSet: v.selectedParentChainSet,
|
||||
}
|
||||
}
|
||||
|
||||
// setTips replaces the tips of the virtual block with the blocks in the
|
||||
// given blockSet. This only differs from the exported version in that it
|
||||
// is up to the caller to ensure the lock is held.
|
||||
@@ -122,8 +113,8 @@ func (v *virtualBlock) updateSelectedParentSet(oldSelectedParent *blockNode) *ch
|
||||
// This function is safe for concurrent access.
|
||||
func (v *virtualBlock) SetTips(tips blockSet) {
|
||||
v.mtx.Lock()
|
||||
defer v.mtx.Unlock()
|
||||
v.setTips(tips)
|
||||
v.mtx.Unlock()
|
||||
}
|
||||
|
||||
// addTip adds the given tip to the set of tips in the virtual block.
|
||||
|
||||
@@ -52,12 +52,12 @@ func TestVirtualBlock(t *testing.T) {
|
||||
// \ X
|
||||
// <- 4 <- 6
|
||||
node0 := dag.genesis
|
||||
node1 := buildNode(t, dag, setFromSlice(node0))
|
||||
node2 := buildNode(t, dag, setFromSlice(node1))
|
||||
node3 := buildNode(t, dag, setFromSlice(node0))
|
||||
node4 := buildNode(t, dag, setFromSlice(node0))
|
||||
node5 := buildNode(t, dag, setFromSlice(node3, node4))
|
||||
node6 := buildNode(t, dag, setFromSlice(node3, node4))
|
||||
node1 := buildNode(t, dag, blockSetFromSlice(node0))
|
||||
node2 := buildNode(t, dag, blockSetFromSlice(node1))
|
||||
node3 := buildNode(t, dag, blockSetFromSlice(node0))
|
||||
node4 := buildNode(t, dag, blockSetFromSlice(node0))
|
||||
node5 := buildNode(t, dag, blockSetFromSlice(node3, node4))
|
||||
node6 := buildNode(t, dag, blockSetFromSlice(node3, node4))
|
||||
|
||||
// Given an empty VirtualBlock, each of the following test cases will:
|
||||
// Set its tips to tipsToSet
|
||||
@@ -75,28 +75,28 @@ func TestVirtualBlock(t *testing.T) {
|
||||
name: "empty virtual",
|
||||
tipsToSet: []*blockNode{},
|
||||
tipsToAdd: []*blockNode{},
|
||||
expectedTips: newSet(),
|
||||
expectedTips: newBlockSet(),
|
||||
expectedSelectedParent: nil,
|
||||
},
|
||||
{
|
||||
name: "virtual with genesis tip",
|
||||
tipsToSet: []*blockNode{node0},
|
||||
tipsToAdd: []*blockNode{},
|
||||
expectedTips: setFromSlice(node0),
|
||||
expectedTips: blockSetFromSlice(node0),
|
||||
expectedSelectedParent: node0,
|
||||
},
|
||||
{
|
||||
name: "virtual with genesis tip, add child of genesis",
|
||||
tipsToSet: []*blockNode{node0},
|
||||
tipsToAdd: []*blockNode{node1},
|
||||
expectedTips: setFromSlice(node1),
|
||||
expectedTips: blockSetFromSlice(node1),
|
||||
expectedSelectedParent: node1,
|
||||
},
|
||||
{
|
||||
name: "empty virtual, add a full DAG",
|
||||
tipsToSet: []*blockNode{},
|
||||
tipsToAdd: []*blockNode{node0, node1, node2, node3, node4, node5, node6},
|
||||
expectedTips: setFromSlice(node2, node5, node6),
|
||||
expectedTips: blockSetFromSlice(node2, node5, node6),
|
||||
expectedSelectedParent: node5,
|
||||
},
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func TestVirtualBlock(t *testing.T) {
|
||||
virtual := newVirtualBlock(dag, nil)
|
||||
|
||||
// Set the tips. This will be the initial state
|
||||
virtual.SetTips(setFromSlice(test.tipsToSet...))
|
||||
virtual.SetTips(blockSetFromSlice(test.tipsToSet...))
|
||||
|
||||
// Add all blockNodes in tipsToAdd in order
|
||||
for _, tipToAdd := range test.tipsToAdd {
|
||||
@@ -147,9 +147,9 @@ func TestSelectedPath(t *testing.T) {
|
||||
|
||||
tip := dag.genesis
|
||||
virtual.AddTip(tip)
|
||||
initialPath := setFromSlice(tip)
|
||||
initialPath := blockSetFromSlice(tip)
|
||||
for i := 0; i < 5; i++ {
|
||||
tip = buildNode(t, dag, setFromSlice(tip))
|
||||
tip = buildNode(t, dag, blockSetFromSlice(tip))
|
||||
initialPath.add(tip)
|
||||
virtual.AddTip(tip)
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func TestSelectedPath(t *testing.T) {
|
||||
|
||||
firstPath := initialPath.clone()
|
||||
for i := 0; i < 5; i++ {
|
||||
tip = buildNode(t, dag, setFromSlice(tip))
|
||||
tip = buildNode(t, dag, blockSetFromSlice(tip))
|
||||
firstPath.add(tip)
|
||||
virtual.AddTip(tip)
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func TestSelectedPath(t *testing.T) {
|
||||
secondPath := initialPath.clone()
|
||||
tip = initialTip
|
||||
for i := 0; i < 100; i++ {
|
||||
tip = buildNode(t, dag, setFromSlice(tip))
|
||||
tip = buildNode(t, dag, blockSetFromSlice(tip))
|
||||
secondPath.add(tip)
|
||||
virtual.AddTip(tip)
|
||||
}
|
||||
@@ -193,7 +193,7 @@ func TestSelectedPath(t *testing.T) {
|
||||
|
||||
tip = initialTip
|
||||
for i := 0; i < 3; i++ {
|
||||
tip = buildNode(t, dag, setFromSlice(tip))
|
||||
tip = buildNode(t, dag, blockSetFromSlice(tip))
|
||||
virtual.AddTip(tip)
|
||||
}
|
||||
// Because we added a very short chain, the selected path should not be affected.
|
||||
@@ -215,7 +215,7 @@ func TestSelectedPath(t *testing.T) {
|
||||
t.Fatalf("updateSelectedParentSet didn't panic")
|
||||
}
|
||||
}()
|
||||
virtual2.updateSelectedParentSet(buildNode(t, dag, setFromSlice()))
|
||||
virtual2.updateSelectedParentSet(buildNode(t, dag, blockSetFromSlice()))
|
||||
}
|
||||
|
||||
func TestChainUpdates(t *testing.T) {
|
||||
@@ -236,23 +236,23 @@ func TestChainUpdates(t *testing.T) {
|
||||
var toBeRemovedNodes []*blockNode
|
||||
toBeRemovedTip := genesis
|
||||
for i := 0; i < 5; i++ {
|
||||
toBeRemovedTip = buildNode(t, dag, setFromSlice(toBeRemovedTip))
|
||||
toBeRemovedTip = buildNode(t, dag, blockSetFromSlice(toBeRemovedTip))
|
||||
toBeRemovedNodes = append(toBeRemovedNodes, toBeRemovedTip)
|
||||
}
|
||||
|
||||
// Create a VirtualBlock with the toBeRemoved chain
|
||||
virtual := newVirtualBlock(dag, setFromSlice(toBeRemovedNodes...))
|
||||
virtual := newVirtualBlock(dag, blockSetFromSlice(toBeRemovedNodes...))
|
||||
|
||||
// Create a chain to be added
|
||||
var toBeAddedNodes []*blockNode
|
||||
toBeAddedTip := genesis
|
||||
for i := 0; i < 8; i++ {
|
||||
toBeAddedTip = buildNode(t, dag, setFromSlice(toBeAddedTip))
|
||||
toBeAddedTip = buildNode(t, dag, blockSetFromSlice(toBeAddedTip))
|
||||
toBeAddedNodes = append(toBeAddedNodes, toBeAddedTip)
|
||||
}
|
||||
|
||||
// Set the virtual tip to be the tip of the toBeAdded chain
|
||||
chainUpdates := virtual.setTips(setFromSlice(toBeAddedTip))
|
||||
chainUpdates := virtual.setTips(blockSetFromSlice(toBeAddedTip))
|
||||
|
||||
// Make sure that the removed blocks are as expected (in reverse order)
|
||||
if len(chainUpdates.removedChainBlockHashes) != len(toBeRemovedNodes) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -16,28 +17,29 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// blockDbNamePrefix is the prefix for the kaspad block database.
|
||||
blockDbNamePrefix = "blocks"
|
||||
// blockDBNamePrefix is the prefix for the kaspad block database.
|
||||
blockDBNamePrefix = "blocks"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg *ConfigFlags
|
||||
log logs.Logger
|
||||
log *logs.Logger
|
||||
spawn func(func())
|
||||
)
|
||||
|
||||
// loadBlockDB opens the block database and returns a handle to it.
|
||||
func loadBlockDB() (database.DB, error) {
|
||||
// The database name is based on the database type.
|
||||
dbName := blockDbNamePrefix + "_" + cfg.DbType
|
||||
dbName := blockDBNamePrefix + "_" + cfg.DBType
|
||||
dbPath := filepath.Join(cfg.DataDir, dbName)
|
||||
|
||||
log.Infof("Loading block database from '%s'", dbPath)
|
||||
db, err := database.Open(cfg.DbType, dbPath, ActiveConfig().NetParams().Net)
|
||||
db, err := database.Open(cfg.DBType, dbPath, ActiveConfig().NetParams().Net)
|
||||
if err != nil {
|
||||
// Return the error if it's not because the database doesn't
|
||||
// exist.
|
||||
if dbErr, ok := err.(database.Error); !ok || dbErr.ErrorCode !=
|
||||
var dbErr database.Error
|
||||
if ok := errors.As(err, &dbErr); !ok || dbErr.ErrorCode !=
|
||||
database.ErrDbDoesNotExist {
|
||||
|
||||
return nil, err
|
||||
@@ -48,7 +50,7 @@ func loadBlockDB() (database.DB, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db, err = database.Create(cfg.DbType, dbPath, ActiveConfig().NetParams().Net)
|
||||
db, err = database.Create(cfg.DBType, dbPath, ActiveConfig().NetParams().Net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDbType = "ffldb"
|
||||
defaultDBType = "ffldb"
|
||||
defaultDataFile = "bootstrap.dat"
|
||||
defaultProgress = 10
|
||||
)
|
||||
@@ -40,12 +40,11 @@ func ActiveConfig() *ConfigFlags {
|
||||
//
|
||||
// See loadConfig for details on the configuration load process.
|
||||
type ConfigFlags struct {
|
||||
DataDir string `short:"b" long:"datadir" description:"Location of the kaspad data directory"`
|
||||
DbType string `long:"dbtype" description:"Database backend to use for the Block DAG"`
|
||||
InFile string `short:"i" long:"infile" description:"File containing the block(s)"`
|
||||
TxIndex bool `long:"txindex" description:"Build a full hash-based transaction index which makes all transactions available via the getrawtransaction RPC"`
|
||||
AddrIndex bool `long:"addrindex" description:"Build a full address-based transaction index which makes the searchrawtransactions RPC available"`
|
||||
Progress int `short:"p" long:"progress" description:"Show a progress message each time this number of seconds have passed -- Use 0 to disable progress announcements"`
|
||||
DataDir string `short:"b" long:"datadir" description:"Location of the kaspad data directory"`
|
||||
DBType string `long:"dbtype" description:"Database backend to use for the Block DAG"`
|
||||
InFile string `short:"i" long:"infile" description:"File containing the block(s)"`
|
||||
Progress int `short:"p" long:"progress" description:"Show a progress message each time this number of seconds have passed -- Use 0 to disable progress announcements"`
|
||||
AcceptanceIndex bool `long:"acceptanceindex" description:"Maintain a full hash-based acceptance index which makes the getChainFromBlock RPC available"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
@@ -75,7 +74,7 @@ func loadConfig() (*ConfigFlags, []string, error) {
|
||||
// Default config.
|
||||
activeConfig = &ConfigFlags{
|
||||
DataDir: defaultDataDir,
|
||||
DbType: defaultDbType,
|
||||
DBType: defaultDBType,
|
||||
InFile: defaultDataFile,
|
||||
Progress: defaultProgress,
|
||||
}
|
||||
@@ -84,7 +83,8 @@ func loadConfig() (*ConfigFlags, []string, error) {
|
||||
parser := flags.NewParser(&activeConfig, flags.Default)
|
||||
remainingArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
var flagsErr *flags.Error
|
||||
if ok := errors.As(err, &flagsErr); !ok || flagsErr.Type != flags.ErrHelp {
|
||||
parser.WriteHelp(os.Stderr)
|
||||
}
|
||||
return nil, nil, err
|
||||
@@ -96,10 +96,10 @@ func loadConfig() (*ConfigFlags, []string, error) {
|
||||
}
|
||||
|
||||
// Validate database type.
|
||||
if !validDbType(activeConfig.DbType) {
|
||||
if !validDbType(activeConfig.DBType) {
|
||||
str := "%s: The specified database type [%s] is invalid -- " +
|
||||
"supported types %s"
|
||||
err := errors.Errorf(str, "loadConfig", activeConfig.DbType, strings.Join(knownDbTypes, ", "))
|
||||
err := errors.Errorf(str, "loadConfig", activeConfig.DBType, strings.Join(knownDbTypes, ", "))
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
parser.WriteHelp(os.Stderr)
|
||||
return nil, nil, err
|
||||
|
||||
@@ -6,13 +6,13 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/kaspad/blockdag/indexers"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/blockdag/indexers"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
@@ -101,14 +101,14 @@ func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
|
||||
|
||||
// Skip blocks that already exist.
|
||||
blockHash := block.Hash()
|
||||
if bi.dag.HaveBlock(blockHash) {
|
||||
if bi.dag.IsKnownBlock(blockHash) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Don't bother trying to process orphans.
|
||||
parentHashes := block.MsgBlock().Header.ParentHashes
|
||||
if len(parentHashes) > 0 {
|
||||
if !bi.dag.HaveBlocks(parentHashes) {
|
||||
if !bi.dag.AreKnownBlocks(parentHashes) {
|
||||
return false, errors.Errorf("import file contains block "+
|
||||
"%v which does not link to the available "+
|
||||
"block DAG", parentHashes)
|
||||
@@ -288,28 +288,11 @@ func (bi *blockImporter) Import() chan *importResults {
|
||||
// newBlockImporter returns a new importer for the provided file reader seeker
|
||||
// and database.
|
||||
func newBlockImporter(db database.DB, r io.ReadSeeker) (*blockImporter, error) {
|
||||
// Create the transaction and address indexes if needed.
|
||||
//
|
||||
// CAUTION: the txindex needs to be first in the indexes array because
|
||||
// the addrindex uses data from the txindex during catchup. If the
|
||||
// addrindex is run first, it may not have the transactions from the
|
||||
// current block indexed.
|
||||
// Create the acceptance index if needed.
|
||||
var indexes []indexers.Indexer
|
||||
if cfg.TxIndex || cfg.AddrIndex {
|
||||
// Enable transaction index if address index is enabled since it
|
||||
// requires it.
|
||||
if !cfg.TxIndex {
|
||||
log.Infof("Transaction index enabled because it is " +
|
||||
"required by the address index")
|
||||
cfg.TxIndex = true
|
||||
} else {
|
||||
log.Info("Transaction index is enabled")
|
||||
}
|
||||
indexes = append(indexes, indexers.NewTxIndex())
|
||||
}
|
||||
if cfg.AddrIndex {
|
||||
log.Info("Address index is enabled")
|
||||
indexes = append(indexes, indexers.NewAddrIndex(ActiveConfig().NetParams()))
|
||||
if cfg.AcceptanceIndex {
|
||||
log.Info("Acceptance index is enabled")
|
||||
indexes = append(indexes, indexers.NewAcceptanceIndex())
|
||||
}
|
||||
|
||||
// Create an index manager if any of the optional indexes are enabled.
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/rpcclient"
|
||||
"github.com/kaspanet/kaspad/rpcmodel"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
getSubnetworkRetryDelay = 5 * time.Second
|
||||
maxGetSubnetworkRetries = 12
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := parseConfig()
|
||||
if err != nil {
|
||||
panic(errors.Errorf("error parsing command-line arguments: %s", err))
|
||||
}
|
||||
|
||||
privateKey, addrPubKeyHash, err := decodeKeys(cfg)
|
||||
if err != nil {
|
||||
panic(errors.Errorf("error decoding public key: %s", err))
|
||||
}
|
||||
|
||||
client, err := connect(cfg)
|
||||
if err != nil {
|
||||
panic(errors.Errorf("could not connect to RPC server: %s", err))
|
||||
}
|
||||
log.Infof("Connected to server %s", cfg.RPCServer)
|
||||
|
||||
fundingOutpoint, fundingTx, err := findUnspentTXO(cfg, client, addrPubKeyHash)
|
||||
if err != nil {
|
||||
panic(errors.Errorf("error finding unspent transactions: %s", err))
|
||||
}
|
||||
if fundingOutpoint == nil || fundingTx == nil {
|
||||
panic(errors.Errorf("could not find any unspent transactions this for key"))
|
||||
}
|
||||
log.Infof("Found transaction to spend: %s:%d", fundingOutpoint.TxID, fundingOutpoint.Index)
|
||||
|
||||
registryTx, err := buildSubnetworkRegistryTx(cfg, fundingOutpoint, fundingTx, privateKey)
|
||||
if err != nil {
|
||||
panic(errors.Errorf("error building subnetwork registry tx: %s", err))
|
||||
}
|
||||
|
||||
_, err = client.SendRawTransaction(registryTx, true)
|
||||
if err != nil {
|
||||
panic(errors.Errorf("failed sending subnetwork registry tx: %s", err))
|
||||
}
|
||||
log.Infof("Successfully sent subnetwork registry transaction")
|
||||
|
||||
subnetworkID, err := blockdag.TxToSubnetworkID(registryTx)
|
||||
if err != nil {
|
||||
panic(errors.Errorf("could not build subnetwork ID: %s", err))
|
||||
}
|
||||
|
||||
err = waitForSubnetworkToBecomeAccepted(client, subnetworkID)
|
||||
if err != nil {
|
||||
panic(errors.Errorf("error waiting for subnetwork to become accepted: %s", err))
|
||||
}
|
||||
log.Infof("Subnetwork '%s' was successfully registered.", subnetworkID)
|
||||
}
|
||||
|
||||
func waitForSubnetworkToBecomeAccepted(client *rpcclient.Client, subnetworkID *subnetworkid.SubnetworkID) error {
|
||||
retries := 0
|
||||
for {
|
||||
_, err := client.GetSubnetwork(subnetworkID.String())
|
||||
if err != nil {
|
||||
if rpcError, ok := err.(*rpcmodel.RPCError); ok && rpcError.Code == rpcmodel.ErrRPCSubnetworkNotFound {
|
||||
log.Infof("Subnetwork not found")
|
||||
|
||||
retries++
|
||||
if retries == maxGetSubnetworkRetries {
|
||||
return errors.Errorf("failed to get subnetwork %d times: %s", maxGetSubnetworkRetries, err)
|
||||
}
|
||||
|
||||
log.Infof("Waiting %d seconds...", int(getSubnetworkRetryDelay.Seconds()))
|
||||
<-time.After(getSubnetworkRetryDelay)
|
||||
continue
|
||||
}
|
||||
return errors.Errorf("failed getting subnetwork: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var activeConfig *ConfigFlags
|
||||
|
||||
// ActiveConfig returns the active configuration struct
|
||||
func ActiveConfig() *ConfigFlags {
|
||||
return activeConfig
|
||||
}
|
||||
|
||||
// ConfigFlags holds the configurations set by the command line argument
|
||||
type ConfigFlags struct {
|
||||
PrivateKey string `short:"k" long:"private-key" description:"Private key" required:"true"`
|
||||
RPCUser string `short:"u" long:"rpcuser" description:"RPC username" required:"true"`
|
||||
RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password" required:"true"`
|
||||
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to" required:"true"`
|
||||
RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
|
||||
DisableTLS bool `long:"notls" description:"Disable TLS"`
|
||||
GasLimit uint64 `long:"gaslimit" description:"The gas limit of the new subnetwork"`
|
||||
RegistryTxFee uint64 `long:"regtxfee" description:"The fee for the subnetwork registry transaction"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
const (
|
||||
defaultSubnetworkGasLimit = 1000
|
||||
defaultRegistryTxFee = 3000
|
||||
)
|
||||
|
||||
func parseConfig() (*ConfigFlags, error) {
|
||||
activeConfig = &ConfigFlags{}
|
||||
parser := flags.NewParser(activeConfig, flags.PrintErrors|flags.HelpFlag)
|
||||
_, err := parser.Parse()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if activeConfig.RPCCert == "" && !activeConfig.DisableTLS {
|
||||
return nil, errors.New("--notls has to be disabled if --cert is used")
|
||||
}
|
||||
|
||||
if activeConfig.RPCCert != "" && activeConfig.DisableTLS {
|
||||
return nil, errors.New("--cert should be omitted if --notls is used")
|
||||
}
|
||||
|
||||
err = activeConfig.ResolveNetwork(parser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if activeConfig.GasLimit < 0 {
|
||||
return nil, errors.Errorf("gaslimit may not be smaller than 0")
|
||||
}
|
||||
if activeConfig.GasLimit == 0 {
|
||||
activeConfig.GasLimit = defaultSubnetworkGasLimit
|
||||
}
|
||||
|
||||
if activeConfig.RegistryTxFee < 0 {
|
||||
return nil, errors.Errorf("regtxfee may not be smaller than 0")
|
||||
}
|
||||
if activeConfig.RegistryTxFee == 0 {
|
||||
activeConfig.RegistryTxFee = defaultRegistryTxFee
|
||||
}
|
||||
|
||||
return activeConfig, nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/rpcclient"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func connect(cfg *ConfigFlags) (*rpcclient.Client, error) {
|
||||
var cert []byte
|
||||
if !cfg.DisableTLS {
|
||||
var err error
|
||||
cert, err = ioutil.ReadFile(cfg.RPCCert)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error reading certificates file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
connCfg := &rpcclient.ConnConfig{
|
||||
Host: cfg.RPCServer,
|
||||
Endpoint: "ws",
|
||||
User: cfg.RPCUser,
|
||||
Pass: cfg.RPCPassword,
|
||||
DisableTLS: cfg.DisableTLS,
|
||||
}
|
||||
|
||||
if !cfg.DisableTLS {
|
||||
connCfg.Certificates = cert
|
||||
}
|
||||
|
||||
client, err := rpcclient.New(connCfg, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error connecting to address %s: %s", cfg.RPCServer, err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/base58"
|
||||
)
|
||||
|
||||
func decodeKeys(cfg *ConfigFlags) (*ecc.PrivateKey, *util.AddressPubKeyHash, error) {
|
||||
privateKeyBytes := base58.Decode(cfg.PrivateKey)
|
||||
privateKey, _ := ecc.PrivKeyFromBytes(ecc.S256(), privateKeyBytes)
|
||||
serializedPrivateKey := privateKey.PubKey().SerializeCompressed()
|
||||
|
||||
addr, err := util.NewAddressPubKeyHashFromPublicKey(serializedPrivateKey, ActiveConfig().NetParams().Prefix)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return privateKey, addr, nil
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/logs"
|
||||
)
|
||||
|
||||
var (
|
||||
backendLog = logs.NewBackend()
|
||||
log = backendLog.Logger("ASUB")
|
||||
)
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func buildSubnetworkRegistryTx(cfg *ConfigFlags, fundingOutpoint *wire.Outpoint, fundingTx *wire.MsgTx, privateKey *ecc.PrivateKey) (*wire.MsgTx, error) {
|
||||
txIn := &wire.TxIn{
|
||||
PreviousOutpoint: *fundingOutpoint,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
}
|
||||
txOut := &wire.TxOut{
|
||||
ScriptPubKey: fundingTx.TxOut[fundingOutpoint.Index].ScriptPubKey,
|
||||
Value: fundingTx.TxOut[fundingOutpoint.Index].Value - cfg.RegistryTxFee,
|
||||
}
|
||||
registryTx := wire.NewRegistryMsgTx(1, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}, cfg.GasLimit)
|
||||
|
||||
SignatureScript, err := txscript.SignatureScript(registryTx, 0, fundingTx.TxOut[fundingOutpoint.Index].ScriptPubKey,
|
||||
txscript.SigHashAll, privateKey, true)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to build signature script: %s", err)
|
||||
}
|
||||
txIn.SignatureScript = SignatureScript
|
||||
|
||||
return registryTx, nil
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"github.com/kaspanet/kaspad/rpcclient"
|
||||
"github.com/kaspanet/kaspad/rpcmodel"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
resultsCount = 1000
|
||||
minConfirmations = 10
|
||||
)
|
||||
|
||||
func findUnspentTXO(cfg *ConfigFlags, client *rpcclient.Client, addrPubKeyHash *util.AddressPubKeyHash) (*wire.Outpoint, *wire.MsgTx, error) {
|
||||
txs, err := collectTransactions(client, addrPubKeyHash)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
utxos := buildUTXOs(txs)
|
||||
for outpoint, tx := range utxos {
|
||||
// Skip TXOs that can't pay for registration
|
||||
if tx.TxOut[outpoint.Index].Value < cfg.RegistryTxFee {
|
||||
continue
|
||||
}
|
||||
|
||||
return &outpoint, tx, nil
|
||||
}
|
||||
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func collectTransactions(client *rpcclient.Client, addrPubKeyHash *util.AddressPubKeyHash) ([]*wire.MsgTx, error) {
|
||||
txs := make([]*wire.MsgTx, 0)
|
||||
skip := 0
|
||||
for {
|
||||
results, err := client.SearchRawTransactionsVerbose(addrPubKeyHash, skip, resultsCount, true, false, nil)
|
||||
if err != nil {
|
||||
// Break when there are no further txs
|
||||
if rpcError, ok := err.(*rpcmodel.RPCError); ok && rpcError.Code == rpcmodel.ErrRPCNoTxInfo {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
// Mempool transactions bring about unnecessary complexity, so
|
||||
// simply don't bother processing them
|
||||
if result.IsInMempool {
|
||||
continue
|
||||
}
|
||||
|
||||
tx, err := parseRawTransactionResult(result)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to process SearchRawTransactionResult: %s", err)
|
||||
}
|
||||
if tx == nil {
|
||||
continue
|
||||
}
|
||||
if !isTxMatured(tx, *result.Confirmations) {
|
||||
continue
|
||||
}
|
||||
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
|
||||
skip += resultsCount
|
||||
}
|
||||
return txs, nil
|
||||
}
|
||||
|
||||
func parseRawTransactionResult(result *rpcmodel.SearchRawTransactionsResult) (*wire.MsgTx, error) {
|
||||
txBytes, err := hex.DecodeString(result.Hex)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to decode transaction bytes: %s", err)
|
||||
}
|
||||
var tx wire.MsgTx
|
||||
reader := bytes.NewReader(txBytes)
|
||||
err = tx.Deserialize(reader)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to deserialize transaction: %s", err)
|
||||
}
|
||||
return &tx, nil
|
||||
}
|
||||
|
||||
func isTxMatured(tx *wire.MsgTx, confirmations uint64) bool {
|
||||
if !tx.IsCoinBase() {
|
||||
return confirmations >= minConfirmations
|
||||
}
|
||||
return confirmations >= ActiveConfig().NetParams().BlockCoinbaseMaturity
|
||||
}
|
||||
|
||||
func buildUTXOs(txs []*wire.MsgTx) map[wire.Outpoint]*wire.MsgTx {
|
||||
utxos := make(map[wire.Outpoint]*wire.MsgTx)
|
||||
for _, tx := range txs {
|
||||
for i := range tx.TxOut {
|
||||
outpoint := wire.NewOutpoint(tx.TxID(), uint32(i))
|
||||
utxos[*outpoint] = tx
|
||||
}
|
||||
}
|
||||
for _, tx := range txs {
|
||||
for _, input := range tx.TxIn {
|
||||
delete(utxos, input.PreviousOutpoint)
|
||||
}
|
||||
}
|
||||
return utxos
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -32,7 +33,8 @@ func main() {
|
||||
parser := flags.NewParser(&cfg, flags.Default)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
var flagsErr *flags.Error
|
||||
if ok := errors.As(err, &flagsErr); !ok || flagsErr.Type != flags.ErrHelp {
|
||||
parser.WriteHelp(os.Stderr)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -7,9 +7,9 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -36,11 +36,6 @@ var (
|
||||
activeConfig *ConfigFlags
|
||||
)
|
||||
|
||||
// ActiveConfig returns the active configuration struct
|
||||
func ActiveConfig() *ConfigFlags {
|
||||
return activeConfig
|
||||
}
|
||||
|
||||
// listCommands categorizes and lists all of the usable commands along with
|
||||
// their one-line usage.
|
||||
func listCommands() {
|
||||
@@ -108,28 +103,6 @@ type ConfigFlags struct {
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
// normalizeAddress returns addr with the passed default port appended if
|
||||
// there is not already a port specified.
|
||||
func normalizeAddress(addr string, useTestnet, useSimnet, useDevnet bool) string {
|
||||
_, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
var defaultPort string
|
||||
switch {
|
||||
case useDevnet:
|
||||
defaultPort = "16610"
|
||||
case useTestnet:
|
||||
defaultPort = "16210"
|
||||
case useSimnet:
|
||||
defaultPort = "16510"
|
||||
default:
|
||||
defaultPort = "16110"
|
||||
}
|
||||
|
||||
return net.JoinHostPort(addr, defaultPort)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// cleanAndExpandPath expands environement variables and leading ~ in the
|
||||
// passed path, cleans the result, and returns it.
|
||||
func cleanAndExpandPath(path string) string {
|
||||
@@ -172,7 +145,8 @@ func loadConfig() (*ConfigFlags, []string, error) {
|
||||
preParser := flags.NewParser(preCfg, flags.HelpFlag)
|
||||
_, err := preParser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
|
||||
var flagsErr *flags.Error
|
||||
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprintln(os.Stderr, "")
|
||||
fmt.Fprintln(os.Stderr, "The special parameter `-` "+
|
||||
@@ -188,7 +162,7 @@ func loadConfig() (*ConfigFlags, []string, error) {
|
||||
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
||||
usageMessage := fmt.Sprintf("Use %s -h to show options", appName)
|
||||
if preCfg.ShowVersion {
|
||||
fmt.Println(appName, "version", version())
|
||||
fmt.Println(appName, "version", version.Version())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -216,7 +190,7 @@ func loadConfig() (*ConfigFlags, []string, error) {
|
||||
parser := flags.NewParser(activeConfig, flags.Default)
|
||||
err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
if pErr := &(os.PathError{}); !errors.As(err, &pErr) {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing config file: %s\n",
|
||||
err)
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
@@ -227,7 +201,8 @@ func loadConfig() (*ConfigFlags, []string, error) {
|
||||
// Parse command line options again to ensure they take precedence.
|
||||
remainingArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
var flagsErr *flags.Error
|
||||
if ok := errors.As(err, &flagsErr); !ok || flagsErr.Type != flags.ErrHelp {
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
}
|
||||
return nil, nil, err
|
||||
@@ -242,8 +217,10 @@ func loadConfig() (*ConfigFlags, []string, error) {
|
||||
|
||||
// Add default port to RPC server based on --testnet and --simnet flags
|
||||
// if needed.
|
||||
activeConfig.RPCServer = normalizeAddress(activeConfig.RPCServer, activeConfig.Testnet,
|
||||
activeConfig.Simnet, activeConfig.Devnet)
|
||||
activeConfig.RPCServer, err = activeConfig.NetParams().NormalizeRPCServerAddress(activeConfig.RPCServer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return activeConfig, remainingArgs, nil
|
||||
}
|
||||
|
||||
@@ -95,11 +95,12 @@ func sendPostRequest(marshalledJSON []byte, cfg *ConfigFlags) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Read the raw bytes and close the response.
|
||||
respBytes, err := ioutil.ReadAll(httpResponse.Body)
|
||||
httpResponse.Body.Close()
|
||||
respBytes, err := func() ([]byte, error) {
|
||||
defer httpResponse.Body.Close()
|
||||
return ioutil.ReadAll(httpResponse.Body)
|
||||
}()
|
||||
if err != nil {
|
||||
err = errors.Errorf("error reading json reply: %s", err)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "error reading json reply")
|
||||
}
|
||||
|
||||
// Handle unsuccessful HTTP responses
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -110,9 +111,10 @@ func main() {
|
||||
// rpcmodel.Error as it reallistcally will always be since the
|
||||
// NewCommand function is only supposed to return errors of that
|
||||
// type.
|
||||
if jerr, ok := err.(rpcmodel.Error); ok {
|
||||
var rpcModelErr rpcmodel.Error
|
||||
if ok := errors.As(err, &rpcModelErr); ok {
|
||||
fmt.Fprintf(os.Stderr, "%s error: %s (command code: %s)\n",
|
||||
method, err, jerr.ErrorCode)
|
||||
method, err, rpcModelErr.ErrorCode)
|
||||
commandUsage(method)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2013 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// semanticAlphabet
|
||||
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||
|
||||
// These constants define the application version and follow the semantic
|
||||
// versioning 2.0.0 spec (http://semver.org/).
|
||||
const (
|
||||
appMajor uint = 0
|
||||
appMinor uint = 12
|
||||
appPatch uint = 0
|
||||
|
||||
// appPreRelease MUST only contain characters from semanticAlphabet
|
||||
// per the semantic versioning spec.
|
||||
appPreRelease = "beta"
|
||||
)
|
||||
|
||||
// appBuild is defined as a variable so it can be overridden during the build
|
||||
// process with '-ldflags "-X main.appBuild foo' if needed. It MUST only
|
||||
// contain characters from semanticAlphabet per the semantic versioning spec.
|
||||
var appBuild string
|
||||
|
||||
// version returns the application version as a properly formed string per the
|
||||
// semantic versioning 2.0.0 spec (http://semver.org/).
|
||||
func version() string {
|
||||
// Start with the major, minor, and patch versions.
|
||||
version := fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch)
|
||||
|
||||
// Append pre-release version if there is one. The hyphen called for
|
||||
// by the semantic versioning spec is automatically appended and should
|
||||
// not be contained in the pre-release string. The pre-release version
|
||||
// is not appended if it contains invalid characters.
|
||||
preRelease := normalizeVerString(appPreRelease)
|
||||
if preRelease != "" {
|
||||
version = fmt.Sprintf("%s-%s", version, preRelease)
|
||||
}
|
||||
|
||||
// Append build metadata if there is any. The plus called for
|
||||
// by the semantic versioning spec is automatically appended and should
|
||||
// not be contained in the build metadata string. The build metadata
|
||||
// string is not appended if it contains invalid characters.
|
||||
build := normalizeVerString(appBuild)
|
||||
if build != "" {
|
||||
version = fmt.Sprintf("%s+%s", version, build)
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// normalizeVerString returns the passed string stripped of all characters which
|
||||
// are not valid according to the semantic versioning guidelines for pre-release
|
||||
// version and build metadata strings. In particular they MUST only contain
|
||||
// characters in semanticAlphabet.
|
||||
func normalizeVerString(str string) string {
|
||||
var result bytes.Buffer
|
||||
for _, r := range str {
|
||||
if strings.ContainsRune(semanticAlphabet, r) {
|
||||
// Ignoring the error here since it can only fail if
|
||||
// the the system is out of memory and there are much
|
||||
// bigger issues at that point.
|
||||
_, _ = result.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -43,8 +42,13 @@ func connectToServer(cfg *configFlags) (*minerClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rpcAddr, err := cfg.NetParams().NormalizeRPCServerAddress(cfg.RPCServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connCfg := &rpcclient.ConnConfig{
|
||||
Host: normalizeRPCServerAddress(cfg.RPCServer, cfg),
|
||||
Host: rpcAddr,
|
||||
Endpoint: "ws",
|
||||
User: cfg.RPCUser,
|
||||
Pass: cfg.RPCPassword,
|
||||
@@ -63,16 +67,6 @@ func connectToServer(cfg *configFlags) (*minerClient, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// normalizeRPCServerAddress returns addr with the current network default
|
||||
// port appended if there is not already a port specified.
|
||||
func normalizeRPCServerAddress(addr string, cfg *configFlags) string {
|
||||
_, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return net.JoinHostPort(addr, cfg.NetParams().RPCPort)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
func readCert(cfg *configFlags) ([]byte, error) {
|
||||
if cfg.DisableTLS {
|
||||
return nil, nil
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspaminer/version"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -28,7 +28,5 @@ func initLog(logFile, errLogFile string) {
|
||||
}
|
||||
|
||||
func enableRPCLogging() {
|
||||
rpclog := backendLog.Logger("RPCC")
|
||||
rpclog.SetLevel(logs.LevelTrace)
|
||||
rpcclient.UseLogger(rpclog)
|
||||
rpcclient.UseLogger(backendLog, logs.LevelTrace)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspaminer/version"
|
||||
"github.com/kaspanet/kaspad/signal"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer panics.HandlePanic(log, nil, nil)
|
||||
defer panics.HandlePanic(log, nil)
|
||||
interrupt := signal.InterruptListener()
|
||||
|
||||
cfg, err := parseConfig()
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/rpcclient"
|
||||
@@ -20,6 +21,9 @@ import (
|
||||
)
|
||||
|
||||
var random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
var hashesTried uint64
|
||||
|
||||
const logHashRateInterval = 10 * time.Second
|
||||
|
||||
func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64) error {
|
||||
errChan := make(chan error)
|
||||
@@ -50,6 +54,8 @@ func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64) err
|
||||
doneChan <- struct{}{}
|
||||
})
|
||||
|
||||
logHashRate()
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
return err
|
||||
@@ -58,10 +64,30 @@ func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64) err
|
||||
}
|
||||
}
|
||||
|
||||
func logHashRate() {
|
||||
spawn(func() {
|
||||
lastCheck := time.Now()
|
||||
for range time.Tick(logHashRateInterval) {
|
||||
currentHashesTried := hashesTried
|
||||
currentTime := time.Now()
|
||||
kiloHashesTried := float64(currentHashesTried) / 1000.0
|
||||
hashRate := kiloHashesTried / currentTime.Sub(lastCheck).Seconds()
|
||||
log.Infof("Current hash rate is %.2f Khash/s", hashRate)
|
||||
lastCheck = currentTime
|
||||
// subtract from hashesTried the hashes we already sampled
|
||||
atomic.AddUint64(&hashesTried, -currentHashesTried)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func mineNextBlock(client *minerClient, foundBlock chan *util.Block, templateStopChan chan struct{}, errChan chan error) {
|
||||
newTemplateChan := make(chan *rpcmodel.GetBlockTemplateResult)
|
||||
go templatesLoop(client, newTemplateChan, errChan, templateStopChan)
|
||||
go solveLoop(newTemplateChan, foundBlock, errChan)
|
||||
spawn(func() {
|
||||
templatesLoop(client, newTemplateChan, errChan, templateStopChan)
|
||||
})
|
||||
spawn(func() {
|
||||
solveLoop(newTemplateChan, foundBlock, errChan)
|
||||
})
|
||||
}
|
||||
|
||||
func handleFoundBlock(client *minerClient, block *util.Block) error {
|
||||
@@ -131,6 +157,7 @@ func solveBlock(block *util.Block, stopChan chan struct{}, foundBlock chan *util
|
||||
default:
|
||||
msgBlock.Header.Nonce = i
|
||||
hash := msgBlock.BlockHash()
|
||||
atomic.AddUint64(&hashesTried, 1)
|
||||
if daghash.HashToBig(hash).Cmp(targetDifficulty) <= 0 {
|
||||
foundBlock <- block
|
||||
return
|
||||
@@ -193,7 +220,9 @@ func solveLoop(newTemplateChan chan *rpcmodel.GetBlockTemplateResult, foundBlock
|
||||
return
|
||||
}
|
||||
|
||||
go solveBlock(block, stopOldTemplateSolving, foundBlock)
|
||||
spawn(func() {
|
||||
solveBlock(block, stopOldTemplateSolving, foundBlock)
|
||||
})
|
||||
}
|
||||
if stopOldTemplateSolving != nil {
|
||||
close(stopOldTemplateSolving)
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// validCharacters is a list of characters valid in the appBuild string
|
||||
const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||
|
||||
const (
|
||||
appMajor uint = 0
|
||||
appMinor uint = 1
|
||||
appPatch uint = 0
|
||||
)
|
||||
|
||||
// appBuild is defined as a variable so it can be overridden during the build
|
||||
// process with '-ldflags "-X github.com/kaspanet/kaspad/cmd/kaspaminer/version.appBuild=foo"' if needed.
|
||||
// It MUST only contain characters from validCharacters.
|
||||
var appBuild string
|
||||
|
||||
var version = "" // string used for memoization of version
|
||||
|
||||
// Version returns the application version as a properly formed string
|
||||
func Version() string {
|
||||
if version == "" {
|
||||
// Start with the major, minor, and patch versions.
|
||||
version = fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch)
|
||||
|
||||
// Append build metadata if there is any.
|
||||
// Panic if any invalid characters are encountered
|
||||
if appBuild != "" {
|
||||
checkAppBuild(appBuild)
|
||||
|
||||
version = fmt.Sprintf("%s-%s", version, appBuild)
|
||||
}
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// checkAppBuild verifies that appBuild does not contain any characters outside of validCharacters.
|
||||
// In case of any invalid characters checkAppBuild panics
|
||||
func checkAppBuild(appBuild string) {
|
||||
for _, r := range appBuild {
|
||||
if !strings.ContainsRune(validCharacters, r) {
|
||||
panic(fmt.Errorf("appBuild string (%s) contains forbidden characters. Only alphanumeric characters and dashes are allowed", appBuild))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -53,6 +54,9 @@ func parsePrivateKey(privateKeyHex string) (*ecc.PrivateKey, error) {
|
||||
|
||||
func parseTransaction(transactionHex string) (*wire.MsgTx, error) {
|
||||
serializedTx, err := hex.DecodeString(transactionHex)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "couldn't decode transaction hex")
|
||||
}
|
||||
var transaction wire.MsgTx
|
||||
err = transaction.Deserialize(bytes.NewReader(serializedTx))
|
||||
return &transaction, err
|
||||
@@ -60,6 +64,9 @@ func parseTransaction(transactionHex string) (*wire.MsgTx, error) {
|
||||
|
||||
func createScriptPubKey(publicKey *ecc.PublicKey) ([]byte, error) {
|
||||
p2pkhAddress, err := util.NewAddressPubKeyHashFromPublicKey(publicKey.SerializeCompressed(), ActiveConfig().NetParams().Prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scriptPubKey, err := txscript.PayToAddrScript(p2pkhAddress)
|
||||
return scriptPubKey, err
|
||||
}
|
||||
|
||||
120
config/config.go
120
config/config.go
@@ -56,8 +56,6 @@ const (
|
||||
DefaultMaxOrphanTxSize = 100000
|
||||
defaultSigCacheMaxSize = 100000
|
||||
sampleConfigFilename = "sample-kaspad.conf"
|
||||
defaultTxIndex = false
|
||||
defaultAddrIndex = false
|
||||
defaultAcceptanceIndex = false
|
||||
)
|
||||
|
||||
@@ -79,15 +77,6 @@ var activeConfig *Config
|
||||
// to parse and execute service commands specified via the -s flag.
|
||||
var RunServiceCommand func(string) error
|
||||
|
||||
// minUint32 is a helper function to return the minimum of two uint32s.
|
||||
// This avoids a math import and the need to cast to floats.
|
||||
func minUint32(a, b uint32) uint32 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Flags defines the configuration options for kaspad.
|
||||
//
|
||||
// See loadConfig for details on the configuration load process.
|
||||
@@ -137,11 +126,7 @@ type Flags struct {
|
||||
NoPeerBloomFilters bool `long:"nopeerbloomfilters" description:"Disable bloom filtering support"`
|
||||
SigCacheMaxSize uint `long:"sigcachemaxsize" description:"The maximum number of entries in the signature verification cache"`
|
||||
BlocksOnly bool `long:"blocksonly" description:"Do not accept transactions from remote peers."`
|
||||
TxIndex bool `long:"txindex" description:"Maintain a full hash-based transaction index which makes all transactions available via the getrawtransaction RPC"`
|
||||
DropTxIndex bool `long:"droptxindex" description:"Deletes the hash-based transaction index from the database on start up and then exits."`
|
||||
AddrIndex bool `long:"addrindex" description:"Maintain a full address-based transaction index which makes the searchrawtransactions RPC available"`
|
||||
DropAddrIndex bool `long:"dropaddrindex" description:"Deletes the address-based transaction index from the database on start up and then exits."`
|
||||
AcceptanceIndex bool `long:"acceptanceindex" description:"Maintain a full hash-based acceptance index which makes the getChainByBlock RPC available"`
|
||||
AcceptanceIndex bool `long:"acceptanceindex" description:"Maintain a full hash-based acceptance index which makes the getChainFromBlock RPC available"`
|
||||
DropAcceptanceIndex bool `long:"dropacceptanceindex" description:"Deletes the hash-based acceptance index from the database on start up and then exits."`
|
||||
RelayNonStd bool `long:"relaynonstd" description:"Relay non-standard transactions regardless of the default settings for the active network."`
|
||||
RejectNonStd bool `long:"rejectnonstd" description:"Reject non-standard transactions regardless of the default settings for the active network."`
|
||||
@@ -218,6 +203,12 @@ func ActiveConfig() *Config {
|
||||
return activeConfig
|
||||
}
|
||||
|
||||
// SetActiveConfig sets the active config
|
||||
// to the given config.
|
||||
func SetActiveConfig(cfg *Config) {
|
||||
activeConfig = cfg
|
||||
}
|
||||
|
||||
// loadConfig initializes and parses the config using a config file and command
|
||||
// line options.
|
||||
//
|
||||
@@ -251,8 +242,6 @@ func loadConfig() (*Config, []string, error) {
|
||||
MaxOrphanTxs: defaultMaxOrphanTransactions,
|
||||
SigCacheMaxSize: defaultSigCacheMaxSize,
|
||||
MinRelayTxFee: defaultMinRelayTxFee,
|
||||
TxIndex: defaultTxIndex,
|
||||
AddrIndex: defaultAddrIndex,
|
||||
AcceptanceIndex: defaultAcceptanceIndex,
|
||||
}
|
||||
|
||||
@@ -267,7 +256,8 @@ func loadConfig() (*Config, []string, error) {
|
||||
preParser := newConfigParser(&preCfg, &serviceOpts, flags.HelpFlag)
|
||||
_, err := preParser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
|
||||
var flagsErr *flags.Error
|
||||
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -313,7 +303,7 @@ func loadConfig() (*Config, []string, error) {
|
||||
|
||||
err := flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
if pErr := &(os.PathError{}); !errors.As(err, &pErr) {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing config "+
|
||||
"file: %s\n", err)
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
@@ -331,7 +321,8 @@ func loadConfig() (*Config, []string, error) {
|
||||
// Parse command line options again to ensure they take precedence.
|
||||
remainingArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
var flagsErr *flags.Error
|
||||
if ok := errors.As(err, &flagsErr); !ok || flagsErr.Type != flags.ErrHelp {
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
}
|
||||
return nil, nil, err
|
||||
@@ -344,7 +335,8 @@ func loadConfig() (*Config, []string, error) {
|
||||
// Show a nicer error message if it's because a symlink is
|
||||
// linked to a directory that does not exist (probably because
|
||||
// it's not mounted).
|
||||
if e, ok := err.(*os.PathError); ok && os.IsExist(err) {
|
||||
var e *os.PathError
|
||||
if ok := errors.As(err, &e); ok && os.IsExist(err) {
|
||||
if link, lerr := os.Readlink(e.Path); lerr == nil {
|
||||
str := "is symlink %s -> %s mounted?"
|
||||
err = errors.Errorf(str, e.Path, link)
|
||||
@@ -588,7 +580,7 @@ func loadConfig() (*Config, []string, error) {
|
||||
}
|
||||
|
||||
// Disallow 0 and negative min tx fees.
|
||||
if activeConfig.MinRelayTxFee <= 0 {
|
||||
if activeConfig.MinRelayTxFee == 0 {
|
||||
str := "%s: The minrelaytxfee option must be greater than 0 -- parsed [%d]"
|
||||
err := errors.Errorf(str, funcName, activeConfig.MinRelayTxFee)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
@@ -631,38 +623,6 @@ func loadConfig() (*Config, []string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// --txindex and --droptxindex do not mix.
|
||||
if activeConfig.TxIndex && activeConfig.DropTxIndex {
|
||||
err := errors.Errorf("%s: the --txindex and --droptxindex "+
|
||||
"options may not be activated at the same time",
|
||||
funcName)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// --addrindex and --dropaddrindex do not mix.
|
||||
if activeConfig.AddrIndex && activeConfig.DropAddrIndex {
|
||||
err := errors.Errorf("%s: the --addrindex and --dropaddrindex "+
|
||||
"options may not be activated at the same time",
|
||||
funcName)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// --addrindex and --droptxindex do not mix.
|
||||
if activeConfig.AddrIndex && activeConfig.DropTxIndex {
|
||||
err := errors.Errorf("%s: the --addrindex and --droptxindex "+
|
||||
"options may not be activated at the same time "+
|
||||
"because the address index relies on the transaction "+
|
||||
"index",
|
||||
funcName)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// --acceptanceindex and --dropacceptanceindex do not mix.
|
||||
if activeConfig.AcceptanceIndex && activeConfig.DropAcceptanceIndex {
|
||||
err := errors.Errorf("%s: the --acceptanceindex and --dropacceptanceindex "+
|
||||
@@ -705,13 +665,19 @@ func loadConfig() (*Config, []string, error) {
|
||||
|
||||
// Add default port to all listener addresses if needed and remove
|
||||
// duplicate addresses.
|
||||
activeConfig.Listeners = network.NormalizeAddresses(activeConfig.Listeners,
|
||||
activeConfig.Listeners, err = network.NormalizeAddresses(activeConfig.Listeners,
|
||||
activeConfig.NetParams().DefaultPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Add default port to all rpc listener addresses if needed and remove
|
||||
// duplicate addresses.
|
||||
activeConfig.RPCListeners = network.NormalizeAddresses(activeConfig.RPCListeners,
|
||||
activeConfig.RPCListeners, err = network.NormalizeAddresses(activeConfig.RPCListeners,
|
||||
activeConfig.NetParams().RPCPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Only allow TLS to be disabled if the RPC is bound to localhost
|
||||
// addresses.
|
||||
@@ -745,10 +711,17 @@ func loadConfig() (*Config, []string, error) {
|
||||
|
||||
// Add default port to all added peer addresses if needed and remove
|
||||
// duplicate addresses.
|
||||
activeConfig.AddPeers = network.NormalizeAddresses(activeConfig.AddPeers,
|
||||
activeConfig.AddPeers, err = network.NormalizeAddresses(activeConfig.AddPeers,
|
||||
activeConfig.NetParams().DefaultPort)
|
||||
activeConfig.ConnectPeers = network.NormalizeAddresses(activeConfig.ConnectPeers,
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
activeConfig.ConnectPeers, err = network.NormalizeAddresses(activeConfig.ConnectPeers,
|
||||
activeConfig.NetParams().DefaultPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Setup dial and DNS resolution (lookup) functions depending on the
|
||||
// specified options. The default is to use the standard
|
||||
@@ -808,18 +781,14 @@ func createDefaultConfigFile(destinationPath string) error {
|
||||
return err
|
||||
}
|
||||
generatedRPCUser := base64.StdEncoding.EncodeToString(randomBytes)
|
||||
rpcUserString := fmt.Sprintf("rpcuser=%s\n", generatedRPCUser)
|
||||
|
||||
_, err = rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
generatedRPCPass := base64.StdEncoding.EncodeToString(randomBytes)
|
||||
|
||||
src, err := os.Open(sampleConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
rpcPassString := fmt.Sprintf("rpcpass=%s\n", generatedRPCPass)
|
||||
|
||||
dest, err := os.OpenFile(destinationPath,
|
||||
os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
@@ -828,6 +797,23 @@ func createDefaultConfigFile(destinationPath string) error {
|
||||
}
|
||||
defer dest.Close()
|
||||
|
||||
// If the sample config file is missing because e.g. kaspad was
|
||||
// installed using go install, simply create the destination
|
||||
// file and write the RPC credentials into it as is.
|
||||
if _, err := os.Stat(sampleConfigPath); os.IsNotExist(err) {
|
||||
toWrite := rpcUserString + rpcPassString
|
||||
if _, err := dest.WriteString(toWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
src, err := os.Open(sampleConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// We copy every line from the sample config file to the destination,
|
||||
// only replacing the two lines for rpcuser and rpcpass
|
||||
reader := bufio.NewReader(src)
|
||||
@@ -839,9 +825,9 @@ func createDefaultConfigFile(destinationPath string) error {
|
||||
}
|
||||
|
||||
if strings.Contains(line, "rpcuser=") {
|
||||
line = "rpcuser=" + generatedRPCUser + "\n"
|
||||
line = rpcUserString
|
||||
} else if strings.Contains(line, "rpcpass=") {
|
||||
line = "rpcpass=" + generatedRPCPass + "\n"
|
||||
line = rpcPassString
|
||||
}
|
||||
|
||||
if _, err := dest.WriteString(line); err != nil {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
connmgr
|
||||
=======
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad/connmgr)
|
||||
|
||||
Package connmgr implements a generic Kaspa network connection manager.
|
||||
|
||||
@@ -89,8 +89,8 @@ type ConnReq struct {
|
||||
// updateState updates the state of the connection request.
|
||||
func (c *ConnReq) updateState(state ConnState) {
|
||||
c.stateMtx.Lock()
|
||||
defer c.stateMtx.Unlock()
|
||||
c.state = state
|
||||
c.stateMtx.Unlock()
|
||||
}
|
||||
|
||||
// ID returns a unique identifier for the connection request.
|
||||
@@ -101,8 +101,8 @@ func (c *ConnReq) ID() uint64 {
|
||||
// State is the connection state of the requested connection.
|
||||
func (c *ConnReq) State() ConnState {
|
||||
c.stateMtx.RLock()
|
||||
defer c.stateMtx.RUnlock()
|
||||
state := c.state
|
||||
c.stateMtx.RUnlock()
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ func (cm *ConnManager) handleFailedConn(c *ConnReq, err error) {
|
||||
}
|
||||
spawnAfter(d, func() {
|
||||
cm.Connect(c)
|
||||
}, nil)
|
||||
})
|
||||
} else if cm.cfg.GetNewAddress != nil {
|
||||
cm.failedAttempts++
|
||||
if cm.failedAttempts >= maxFailedAttempts {
|
||||
@@ -243,9 +243,9 @@ func (cm *ConnManager) handleFailedConn(c *ConnReq, err error) {
|
||||
"-- retrying further connections every %s", maxFailedAttempts,
|
||||
cm.cfg.RetryDuration)
|
||||
}
|
||||
spawnAfter(cm.cfg.RetryDuration, cm.NewConnReq, cm.handlePanic)
|
||||
spawnAfter(cm.cfg.RetryDuration, cm.NewConnReq)
|
||||
} else {
|
||||
spawn(cm.NewConnReq, cm.handlePanic)
|
||||
spawn(cm.NewConnReq)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,7 +336,6 @@ out:
|
||||
conns[connReq.id] = connReq
|
||||
log.Debugf("Connected to %s", connReq)
|
||||
connReq.retryCount = 0
|
||||
cm.failedAttempts = 0
|
||||
|
||||
delete(pending, connReq.id)
|
||||
|
||||
@@ -376,7 +375,9 @@ out:
|
||||
}
|
||||
|
||||
if cm.cfg.OnDisconnection != nil {
|
||||
go cm.cfg.OnDisconnection(connReq)
|
||||
spawn(func() {
|
||||
cm.cfg.OnDisconnection(connReq)
|
||||
})
|
||||
}
|
||||
|
||||
// All internal state has been cleaned up, if
|
||||
@@ -429,6 +430,13 @@ out:
|
||||
log.Trace("Connection handler done")
|
||||
}
|
||||
|
||||
// NotifyConnectionRequestComplete notifies the connection
|
||||
// manager that a peer had been successfully connected and
|
||||
// marked as good.
|
||||
func (cm *ConnManager) NotifyConnectionRequestComplete() {
|
||||
cm.failedAttempts = 0
|
||||
}
|
||||
|
||||
// NewConnReq creates a new connection request and connects to the
|
||||
// corresponding address.
|
||||
func (cm *ConnManager) NewConnReq() {
|
||||
@@ -566,7 +574,9 @@ func (cm *ConnManager) listenHandler(listener net.Listener) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
go cm.cfg.OnAccept(conn)
|
||||
spawn(func() {
|
||||
cm.cfg.OnAccept(conn)
|
||||
})
|
||||
}
|
||||
|
||||
cm.wg.Done()
|
||||
@@ -582,19 +592,24 @@ func (cm *ConnManager) Start() {
|
||||
|
||||
log.Trace("Connection manager started")
|
||||
cm.wg.Add(1)
|
||||
spawn(cm.connHandler, cm.handlePanic)
|
||||
spawn(cm.connHandler)
|
||||
|
||||
// Start all the listeners so long as the caller requested them and
|
||||
// provided a callback to be invoked when connections are accepted.
|
||||
if cm.cfg.OnAccept != nil {
|
||||
for _, listner := range cm.cfg.Listeners {
|
||||
for _, listener := range cm.cfg.Listeners {
|
||||
// Declaring this variable is necessary as it needs be declared in the same
|
||||
// scope of the anonymous function below it.
|
||||
listenerCopy := listener
|
||||
cm.wg.Add(1)
|
||||
go cm.listenHandler(listner)
|
||||
spawn(func() {
|
||||
cm.listenHandler(listenerCopy)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for i := atomic.LoadUint64(&cm.connReqCount); i < uint64(cm.cfg.TargetOutbound); i++ {
|
||||
spawn(cm.NewConnReq, cm.handlePanic)
|
||||
spawn(cm.NewConnReq)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,10 +618,6 @@ func (cm *ConnManager) Wait() {
|
||||
cm.wg.Wait()
|
||||
}
|
||||
|
||||
func (cm *ConnManager) handlePanic() {
|
||||
atomic.AddInt32(&cm.stop, 1)
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down the connection manager.
|
||||
func (cm *ConnManager) Stop() {
|
||||
if atomic.AddInt32(&cm.stop, 1) != 1 {
|
||||
|
||||
@@ -68,9 +68,9 @@ type DynamicBanScore struct {
|
||||
// String returns the ban score as a human-readable string.
|
||||
func (s *DynamicBanScore) String() string {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
r := fmt.Sprintf("persistent %d + transient %f at %d = %d as of now",
|
||||
s.persistent, s.transient, s.lastUnix, s.Int())
|
||||
s.mtx.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ func (s *DynamicBanScore) String() string {
|
||||
// This function is safe for concurrent access.
|
||||
func (s *DynamicBanScore) Int() uint32 {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
r := s.int(time.Now())
|
||||
s.mtx.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ func (s *DynamicBanScore) Int() uint32 {
|
||||
// This function is safe for concurrent access.
|
||||
func (s *DynamicBanScore) Increase(persistent, transient uint32) uint32 {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
r := s.increase(persistent, transient, time.Now())
|
||||
s.mtx.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -101,10 +101,10 @@ func (s *DynamicBanScore) Increase(persistent, transient uint32) uint32 {
|
||||
// This function is safe for concurrent access.
|
||||
func (s *DynamicBanScore) Reset() {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.persistent = 0
|
||||
s.transient = 0
|
||||
s.lastUnix = 0
|
||||
s.mtx.Unlock()
|
||||
}
|
||||
|
||||
// int returns the ban score, the sum of the persistent and decaying scores at a
|
||||
|
||||
@@ -38,8 +38,8 @@ func TestDynamicBanScoreLifetime(t *testing.T) {
|
||||
var bs DynamicBanScore
|
||||
base := time.Now()
|
||||
|
||||
r := bs.increase(0, math.MaxUint32, base)
|
||||
r = bs.int(base.Add(Lifetime * time.Second))
|
||||
bs.increase(0, math.MaxUint32, base)
|
||||
r := bs.int(base.Add(Lifetime * time.Second))
|
||||
if r != 3 { // 3, not 4 due to precision loss and truncating 3.999...
|
||||
t.Errorf("Pre max age check with MaxUint32 failed - %d", r)
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ import (
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.CMGR)
|
||||
var spawn = panics.GoroutineWrapperFuncWithPanicHandler(log)
|
||||
var spawnAfter = panics.AfterFuncWrapperFuncWithPanicHandler(log)
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
var spawnAfter = panics.AfterFuncWrapperFunc(log)
|
||||
|
||||
@@ -66,7 +66,7 @@ func SeedFromDNS(dagParams *dagconfig.Params, reqServices wire.ServiceFlag, incl
|
||||
}
|
||||
}
|
||||
|
||||
go func(host string) {
|
||||
spawn(func() {
|
||||
randSource := mrand.New(mrand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
seedpeers, err := lookupFn(host)
|
||||
@@ -94,6 +94,6 @@ func SeedFromDNS(dagParams *dagconfig.Params, reqServices wire.ServiceFlag, incl
|
||||
}
|
||||
|
||||
seedFn(addresses)
|
||||
}(host)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
dagconfig
|
||||
========
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad/dagconfig)
|
||||
|
||||
Package dagconfig defines DAG configuration parameters for the standard
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package dagconfig
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/network"
|
||||
"math"
|
||||
"math/big"
|
||||
"time"
|
||||
@@ -185,6 +186,12 @@ type Params struct {
|
||||
HDCoinType uint32
|
||||
}
|
||||
|
||||
// NormalizeRPCServerAddress returns addr with the current network default
|
||||
// port appended if there is not already a port specified.
|
||||
func (p *Params) NormalizeRPCServerAddress(addr string) (string, error) {
|
||||
return network.NormalizeAddress(addr, p.RPCPort)
|
||||
}
|
||||
|
||||
// MainnetParams defines the network parameters for the main Kaspa network.
|
||||
var MainnetParams = Params{
|
||||
K: ghostdagK,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
database
|
||||
========
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad/database)
|
||||
|
||||
Package database provides a block and metadata storage database.
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
// importCmd defines the configuration options for the insecureimport command.
|
||||
type importCmd struct {
|
||||
InFile string `short:"i" long:"infile" description:"File containing the block(s)"`
|
||||
Progress int `short:"p" long:"progress" description:"Show a progress message each time this number of seconds have passed -- Use 0 to disable progress announcements"`
|
||||
}
|
||||
|
||||
var (
|
||||
// importCfg defines the configuration options for the command.
|
||||
importCfg = importCmd{
|
||||
InFile: "bootstrap.dat",
|
||||
Progress: 10,
|
||||
}
|
||||
)
|
||||
|
||||
// importResults houses the stats and result as an import operation.
|
||||
type importResults struct {
|
||||
blocksProcessed int64
|
||||
blocksImported int64
|
||||
err error
|
||||
}
|
||||
|
||||
// blockImporter houses information about an ongoing import from a block data
|
||||
// file to the block database.
|
||||
type blockImporter struct {
|
||||
db database.DB
|
||||
r io.ReadSeeker
|
||||
processQueue chan []byte
|
||||
doneChan chan bool
|
||||
errChan chan error
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
blocksProcessed int64
|
||||
blocksImported int64
|
||||
receivedLogBlocks int64
|
||||
receivedLogTx int64
|
||||
lastHeight int64
|
||||
lastBlockTime time.Time
|
||||
lastLogTime time.Time
|
||||
}
|
||||
|
||||
// readBlock reads the next block from the input file.
|
||||
func (bi *blockImporter) readBlock() ([]byte, error) {
|
||||
// The block file format is:
|
||||
// <network> <block length> <serialized block>
|
||||
var net uint32
|
||||
err := binary.Read(bi.r, binary.LittleEndian, &net)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No block and no error means there are no more blocks to read.
|
||||
return nil, nil
|
||||
}
|
||||
if net != uint32(activeNetParams.Net) {
|
||||
return nil, errors.Errorf("network mismatch -- got %x, want %x",
|
||||
net, uint32(activeNetParams.Net))
|
||||
}
|
||||
|
||||
// Read the block length and ensure it is sane.
|
||||
var blockLen uint32
|
||||
if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if blockLen > wire.MaxMessagePayload {
|
||||
return nil, errors.Errorf("block payload of %d bytes is larger "+
|
||||
"than the max allowed %d bytes", blockLen,
|
||||
wire.MaxMessagePayload)
|
||||
}
|
||||
|
||||
serializedBlock := make([]byte, blockLen)
|
||||
if _, err := io.ReadFull(bi.r, serializedBlock); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serializedBlock, nil
|
||||
}
|
||||
|
||||
// processBlock potentially imports the block into the database. It first
|
||||
// deserializes the raw block while checking for errors. Already known blocks
|
||||
// are skipped and orphan blocks are considered errors. Returns whether the
|
||||
// block was imported along with any potential errors.
|
||||
//
|
||||
// NOTE: This is not a safe import as it does not verify DAG rules.
|
||||
func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
|
||||
// Deserialize the block which includes checks for malformed blocks.
|
||||
block, err := util.NewBlockFromBytes(serializedBlock)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// update progress statistics
|
||||
bi.lastBlockTime = block.MsgBlock().Header.Timestamp
|
||||
bi.receivedLogTx += int64(len(block.MsgBlock().Transactions))
|
||||
|
||||
// Skip blocks that already exist.
|
||||
var exists bool
|
||||
err = bi.db.View(func(dbTx database.Tx) error {
|
||||
exists, err = dbTx.HasBlock(block.Hash())
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if exists {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Don't bother trying to process orphans.
|
||||
parentHashes := block.MsgBlock().Header.ParentHashes
|
||||
for _, parentHash := range parentHashes {
|
||||
var exists bool
|
||||
err := bi.db.View(func(dbTx database.Tx) error {
|
||||
exists, err = dbTx.HasBlock(parentHash)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !exists {
|
||||
return false, errors.Errorf("import file contains block "+
|
||||
"%s which does not link to the available "+
|
||||
"block DAG", parentHash)
|
||||
}
|
||||
}
|
||||
|
||||
// Put the blocks into the database with no checking of DAG rules.
|
||||
err = bi.db.Update(func(dbTx database.Tx) error {
|
||||
return dbTx.StoreBlock(block)
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// readHandler is the main handler for reading blocks from the import file.
|
||||
// This allows block processing to take place in parallel with block reads.
|
||||
// It must be run as a goroutine.
|
||||
func (bi *blockImporter) readHandler() {
|
||||
out:
|
||||
for {
|
||||
// Read the next block from the file and if anything goes wrong
|
||||
// notify the status handler with the error and bail.
|
||||
serializedBlock, err := bi.readBlock()
|
||||
if err != nil {
|
||||
bi.errChan <- errors.Errorf("Error reading from input "+
|
||||
"file: %s", err.Error())
|
||||
break out
|
||||
}
|
||||
|
||||
// A nil block with no error means we're done.
|
||||
if serializedBlock == nil {
|
||||
break out
|
||||
}
|
||||
|
||||
// Send the block or quit if we've been signalled to exit by
|
||||
// the status handler due to an error elsewhere.
|
||||
select {
|
||||
case bi.processQueue <- serializedBlock:
|
||||
case <-bi.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
// Close the processing channel to signal no more blocks are coming.
|
||||
close(bi.processQueue)
|
||||
bi.wg.Done()
|
||||
}
|
||||
|
||||
// logProgress logs block progress as an information message. In order to
|
||||
// prevent spam, it limits logging to one message every importCfg.Progress
|
||||
// seconds with duration and totals included.
|
||||
func (bi *blockImporter) logProgress() {
|
||||
bi.receivedLogBlocks++
|
||||
|
||||
now := time.Now()
|
||||
duration := now.Sub(bi.lastLogTime)
|
||||
if duration < time.Second*time.Duration(importCfg.Progress) {
|
||||
return
|
||||
}
|
||||
|
||||
// Truncate the duration to 10s of milliseconds.
|
||||
durationMillis := int64(duration / time.Millisecond)
|
||||
tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
|
||||
|
||||
// Log information about new block height.
|
||||
blockStr := "blocks"
|
||||
if bi.receivedLogBlocks == 1 {
|
||||
blockStr = "block"
|
||||
}
|
||||
txStr := "transactions"
|
||||
if bi.receivedLogTx == 1 {
|
||||
txStr = "transaction"
|
||||
}
|
||||
log.Infof("Processed %d %s in the last %s (%d %s, height %d, %s)",
|
||||
bi.receivedLogBlocks, blockStr, tDuration, bi.receivedLogTx,
|
||||
txStr, bi.lastHeight, bi.lastBlockTime)
|
||||
|
||||
bi.receivedLogBlocks = 0
|
||||
bi.receivedLogTx = 0
|
||||
bi.lastLogTime = now
|
||||
}
|
||||
|
||||
// processHandler is the main handler for processing blocks. This allows block
|
||||
// processing to take place in parallel with block reads from the import file.
|
||||
// It must be run as a goroutine.
|
||||
func (bi *blockImporter) processHandler() {
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case serializedBlock, ok := <-bi.processQueue:
|
||||
// We're done when the channel is closed.
|
||||
if !ok {
|
||||
break out
|
||||
}
|
||||
|
||||
bi.blocksProcessed++
|
||||
bi.lastHeight++
|
||||
imported, err := bi.processBlock(serializedBlock)
|
||||
if err != nil {
|
||||
bi.errChan <- err
|
||||
break out
|
||||
}
|
||||
|
||||
if imported {
|
||||
bi.blocksImported++
|
||||
}
|
||||
|
||||
bi.logProgress()
|
||||
|
||||
case <-bi.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
bi.wg.Done()
|
||||
}
|
||||
|
||||
// statusHandler waits for updates from the import operation and notifies
|
||||
// the passed doneChan with the results of the import. It also causes all
|
||||
// goroutines to exit if an error is reported from any of them.
|
||||
func (bi *blockImporter) statusHandler(resultsChan chan *importResults) {
|
||||
select {
|
||||
// An error from either of the goroutines means we're done so signal
|
||||
// caller with the error and signal all goroutines to quit.
|
||||
case err := <-bi.errChan:
|
||||
resultsChan <- &importResults{
|
||||
blocksProcessed: bi.blocksProcessed,
|
||||
blocksImported: bi.blocksImported,
|
||||
err: err,
|
||||
}
|
||||
close(bi.quit)
|
||||
|
||||
// The import finished normally.
|
||||
case <-bi.doneChan:
|
||||
resultsChan <- &importResults{
|
||||
blocksProcessed: bi.blocksProcessed,
|
||||
blocksImported: bi.blocksImported,
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import is the core function which handles importing the blocks from the file
|
||||
// associated with the block importer to the database. It returns a channel
|
||||
// on which the results will be returned when the operation has completed.
|
||||
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(bi.readHandler)
|
||||
spawn(bi.processHandler)
|
||||
|
||||
// Wait for the import to finish in a separate goroutine and signal
|
||||
// the status handler when done.
|
||||
spawn(func() {
|
||||
bi.wg.Wait()
|
||||
bi.doneChan <- true
|
||||
})
|
||||
|
||||
// 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)
|
||||
go bi.statusHandler(resultChan)
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// newBlockImporter returns a new importer for the provided file reader seeker
|
||||
// and database.
|
||||
func newBlockImporter(db database.DB, r io.ReadSeeker) *blockImporter {
|
||||
return &blockImporter{
|
||||
db: db,
|
||||
r: r,
|
||||
processQueue: make(chan []byte, 2),
|
||||
doneChan: make(chan bool),
|
||||
errChan: make(chan error),
|
||||
quit: make(chan struct{}),
|
||||
lastLogTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// Execute is the main entry point for the command. It's invoked by the parser.
|
||||
func (cmd *importCmd) Execute(args []string) error {
|
||||
// Setup the global config options and ensure they are valid.
|
||||
if err := setupGlobalConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure the specified block file exists.
|
||||
if !fileExists(cmd.InFile) {
|
||||
str := "The specified block file [%s] does not exist"
|
||||
return errors.Errorf(str, cmd.InFile)
|
||||
}
|
||||
|
||||
// Load the block database.
|
||||
db, err := loadBlockDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Ensure the database is sync'd and closed on Ctrl+C.
|
||||
addInterruptHandler(func() {
|
||||
log.Infof("Gracefully shutting down the database...")
|
||||
db.Close()
|
||||
})
|
||||
|
||||
fi, err := os.Open(importCfg.InFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fi.Close()
|
||||
|
||||
// Create a block importer for the database and input file and start it.
|
||||
// The results channel returned from start will contain an error if
|
||||
// anything went wrong.
|
||||
importer := newBlockImporter(db, fi)
|
||||
|
||||
// Perform the import asynchronously and signal the main goroutine when
|
||||
// done. This allows blocks to be processed and read in parallel. The
|
||||
// results channel returned from Import contains the statistics about
|
||||
// the import including an error if something went wrong. This is done
|
||||
// in a separate goroutine rather than waiting directly so the main
|
||||
// goroutine can be signaled for shutdown by either completion, error,
|
||||
// or from the main interrupt handler. This is necessary since the main
|
||||
// goroutine must be kept running long enough for the interrupt handler
|
||||
// goroutine to finish.
|
||||
spawn(func() {
|
||||
log.Info("Starting import")
|
||||
resultsChan := importer.Import()
|
||||
results := <-resultsChan
|
||||
if results.err != nil {
|
||||
dbErr, ok := results.err.(database.Error)
|
||||
if !ok || ok && dbErr.ErrorCode != database.ErrDbNotOpen {
|
||||
shutdownChannel <- results.err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Processed a total of %d blocks (%d imported, %d "+
|
||||
"already known)", results.blocksProcessed,
|
||||
results.blocksImported,
|
||||
results.blocksProcessed-results.blocksImported)
|
||||
shutdownChannel <- nil
|
||||
})
|
||||
|
||||
// Wait for shutdown signal from either a normal completion or from the
|
||||
// interrupt handler.
|
||||
err = <-shutdownChannel
|
||||
return err
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// headersCmd defines the configuration options for the loadheaders command.
|
||||
type headersCmd struct {
|
||||
Bulk bool `long:"bulk" description:"Use bulk loading of headers instead of one at a time"`
|
||||
}
|
||||
|
||||
var (
|
||||
// headersCfg defines the configuration options for the command.
|
||||
headersCfg = headersCmd{
|
||||
Bulk: false,
|
||||
}
|
||||
)
|
||||
|
||||
// Execute is the main entry point for the command. It's invoked by the parser.
|
||||
func (cmd *headersCmd) Execute(args []string) error {
|
||||
// Setup the global config options and ensure they are valid.
|
||||
if err := setupGlobalConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the block database.
|
||||
db, err := loadBlockDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// NOTE: This code will only work for ffldb. Ideally the package using
|
||||
// the database would keep a metadata index of its own.
|
||||
blockIdxName := []byte("ffldb-blockidx")
|
||||
if !headersCfg.Bulk {
|
||||
err = db.View(func(dbTx database.Tx) error {
|
||||
totalHdrs := 0
|
||||
blockIdxBucket := dbTx.Metadata().Bucket(blockIdxName)
|
||||
blockIdxBucket.ForEach(func(k, v []byte) error {
|
||||
totalHdrs++
|
||||
return nil
|
||||
})
|
||||
log.Infof("Loading headers for %d blocks...", totalHdrs)
|
||||
numLoaded := 0
|
||||
startTime := time.Now()
|
||||
blockIdxBucket.ForEach(func(k, v []byte) error {
|
||||
var hash daghash.Hash
|
||||
copy(hash[:], k)
|
||||
_, err := dbTx.FetchBlockHeader(&hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numLoaded++
|
||||
return nil
|
||||
})
|
||||
log.Infof("Loaded %d headers in %s", numLoaded,
|
||||
time.Since(startTime))
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Bulk load headers.
|
||||
err = db.View(func(dbTx database.Tx) error {
|
||||
blockIdxBucket := dbTx.Metadata().Bucket(blockIdxName)
|
||||
hashes := make([]*daghash.Hash, 0, 500000)
|
||||
blockIdxBucket.ForEach(func(k, v []byte) error {
|
||||
var hash daghash.Hash
|
||||
copy(hash[:], k)
|
||||
hashes = append(hashes, &hash)
|
||||
return nil
|
||||
})
|
||||
|
||||
log.Infof("Loading headers for %d blocks...", len(hashes))
|
||||
startTime := time.Now()
|
||||
hdrs, err := dbTx.FetchBlockHeaders(hashes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Loaded %d headers in %s", len(hdrs),
|
||||
time.Since(startTime))
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -23,7 +24,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
log logs.Logger
|
||||
log *logs.Logger
|
||||
spawn func(func())
|
||||
shutdownChannel = make(chan error)
|
||||
)
|
||||
@@ -39,7 +40,8 @@ func loadBlockDB() (database.DB, error) {
|
||||
if err != nil {
|
||||
// Return the error if it's not because the database doesn't
|
||||
// exist.
|
||||
if dbErr, ok := err.(database.Error); !ok || dbErr.ErrorCode !=
|
||||
var dbErr database.Error
|
||||
if ok := errors.As(err, &dbErr); !ok || dbErr.ErrorCode !=
|
||||
database.ErrDbDoesNotExist {
|
||||
|
||||
return nil, err
|
||||
@@ -68,7 +70,7 @@ func realMain() error {
|
||||
defer os.Stdout.Sync()
|
||||
log = backendLogger.Logger("MAIN")
|
||||
spawn = panics.GoroutineWrapperFunc(log)
|
||||
dbLog, _ := logger.Get(logger.SubsystemTags.BCDB)
|
||||
dbLog, _ := logger.Get(logger.SubsystemTags.KSDB)
|
||||
dbLog.SetLevel(logs.LevelDebug)
|
||||
|
||||
// Setup the parser options and commands.
|
||||
@@ -77,15 +79,6 @@ func realMain() error {
|
||||
parserFlags := flags.Options(flags.HelpFlag | flags.PassDoubleDash)
|
||||
parser := flags.NewNamedParser(appName, parserFlags)
|
||||
parser.AddGroup("Global Options", "", cfg)
|
||||
parser.AddCommand("insecureimport",
|
||||
"Insecurely import bulk block data from bootstrap.dat",
|
||||
"Insecurely import bulk block data from bootstrap.dat. "+
|
||||
"WARNING: This is NOT secure because it does NOT "+
|
||||
"verify DAG rules. It is only provided for testing "+
|
||||
"purposes.", &importCfg)
|
||||
parser.AddCommand("loadheaders",
|
||||
"Time how long to load headers for all blocks in the database",
|
||||
"", &headersCfg)
|
||||
parser.AddCommand("fetchblock",
|
||||
"Fetch the specific block hash from the database", "",
|
||||
&fetchBlockCfg)
|
||||
@@ -96,7 +89,8 @@ func realMain() error {
|
||||
// Parse command line and invoke the Execute function for the specified
|
||||
// command.
|
||||
if _, err := parser.Parse(); err != nil {
|
||||
if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
|
||||
var flagsErr *flags.Error
|
||||
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {
|
||||
parser.WriteHelp(os.Stderr)
|
||||
} else {
|
||||
log.Error(err)
|
||||
|
||||
@@ -12,14 +12,6 @@ import (
|
||||
_ "github.com/kaspanet/kaspad/database/ffldb"
|
||||
)
|
||||
|
||||
var (
|
||||
// ignoreDbTypes are types which should be ignored when running tests
|
||||
// that iterate all supported DB types. This allows some tests to add
|
||||
// bogus drivers for testing purposes while still allowing other tests
|
||||
// to easily iterate all supported drivers.
|
||||
ignoreDbTypes = map[string]bool{"createopenfail": true}
|
||||
)
|
||||
|
||||
// checkDbError ensures the passed error is a database.Error with an error code
|
||||
// that matches the passed error code.
|
||||
func checkDbError(t *testing.T, testName string, gotErr error, wantErrCode database.ErrorCode) bool {
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
|
||||
package database
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrorCode identifies a kind of error.
|
||||
type ErrorCode int
|
||||
@@ -199,8 +202,9 @@ func makeError(c ErrorCode, desc string, err error) Error {
|
||||
// IsErrorCode returns whether or not the provided error is a script error with
|
||||
// the provided error code.
|
||||
func IsErrorCode(err error, c ErrorCode) bool {
|
||||
if err, ok := err.(Error); ok {
|
||||
return err.ErrorCode == c
|
||||
var errError Error
|
||||
if ok := errors.As(err, &errError); ok {
|
||||
return errError.ErrorCode == c
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
ffldb
|
||||
=====
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad/database/ffldb)
|
||||
=======
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"container/list"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"os"
|
||||
@@ -36,16 +37,6 @@ const (
|
||||
// must be less than 2^32 (4 GiB). This is also why it's a typed
|
||||
// constant.
|
||||
maxBlockFileSize uint32 = 512 * 1024 * 1024 // 512 MiB
|
||||
|
||||
// blockLocSize is the number of bytes the serialized block location
|
||||
// data that is stored in the block index.
|
||||
//
|
||||
// The serialized block location format is:
|
||||
//
|
||||
// [0:4] Block file (4 bytes)
|
||||
// [4:8] File offset (4 bytes)
|
||||
// [8:12] Block length (4 bytes)
|
||||
blockLocSize = 12
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -379,7 +370,8 @@ func (s *blockStore) writeData(data []byte, fieldName string) error {
|
||||
n, err := wc.curFile.file.WriteAt(data, int64(wc.curOffset))
|
||||
wc.curOffset += uint32(n)
|
||||
if err != nil {
|
||||
if pathErr, isOk := err.(*os.PathError); isOk && pathErr.Err == syscall.ENOSPC {
|
||||
var pathErr *os.PathError
|
||||
if ok := errors.As(err, &pathErr); ok && pathErr.Err == syscall.ENOSPC {
|
||||
log.Errorf("No space left on the hard disk, exiting...")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/goleveldb/leveldb"
|
||||
"github.com/btcsuite/goleveldb/leveldb/filter"
|
||||
"github.com/btcsuite/goleveldb/leveldb/opt"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/filter"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
)
|
||||
|
||||
func newTestDb(testName string, t *testing.T) *db {
|
||||
|
||||
@@ -14,31 +14,22 @@ import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/goleveldb/leveldb"
|
||||
"github.com/btcsuite/goleveldb/leveldb/comparer"
|
||||
ldberrors "github.com/btcsuite/goleveldb/leveldb/errors"
|
||||
"github.com/btcsuite/goleveldb/leveldb/filter"
|
||||
"github.com/btcsuite/goleveldb/leveldb/iterator"
|
||||
"github.com/btcsuite/goleveldb/leveldb/opt"
|
||||
dbutil "github.com/btcsuite/goleveldb/leveldb/util"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/database/internal/treap"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/comparer"
|
||||
ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/filter"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
dbutil "github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataDbName is the name used for the metadata database.
|
||||
metadataDbName = "metadata"
|
||||
|
||||
// blockHdrOffset defines the offsets into a block index row for the
|
||||
// block header.
|
||||
//
|
||||
// The serialized block index row format is:
|
||||
// <blocklocation><blockheader>
|
||||
blockHdrOffset = blockLocSize
|
||||
)
|
||||
// metadataDbName is the name used for the metadata database.
|
||||
const metadataDbName = "metadata"
|
||||
|
||||
var (
|
||||
// byteOrder is the preferred byte order used through the database and
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/goleveldb/leveldb"
|
||||
"github.com/btcsuite/goleveldb/leveldb/iterator"
|
||||
"github.com/btcsuite/goleveldb/leveldb/util"
|
||||
"github.com/kaspanet/kaspad/database/internal/treap"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -24,19 +24,6 @@ const (
|
||||
// threshold in between database cache flushes when the cache size has
|
||||
// not been exceeded.
|
||||
defaultFlushSecs = 300 // 5 minutes
|
||||
|
||||
// ldbBatchHeaderSize is the size of a leveldb batch header which
|
||||
// includes the sequence header and record counter.
|
||||
//
|
||||
// ldbRecordIKeySize is the size of the ikey used internally by leveldb
|
||||
// when appending a record to a batch.
|
||||
//
|
||||
// These are used to help preallocate space needed for a batch in one
|
||||
// allocation instead of letting leveldb itself constantly grow it.
|
||||
// This results in far less pressure on the GC and consequently helps
|
||||
// prevent the GC from allocating a lot of extra unneeded space.
|
||||
ldbBatchHeaderSize = 12
|
||||
ldbRecordIKeySize = 8
|
||||
)
|
||||
|
||||
// ldbCacheIter wraps a treap iterator to provide the additional functionality
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
ldbutil "github.com/btcsuite/goleveldb/leveldb/util"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
ldbutil "github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
func TestExhaustedDbCacheIterator(t *testing.T) {
|
||||
|
||||
@@ -754,7 +754,7 @@ func testMetadataManualTxInterface(tc *testContext) bool {
|
||||
deleteValues := func(values []keyPair) bool {
|
||||
tx, err := tc.db.Begin(true)
|
||||
if err != nil {
|
||||
|
||||
tc.t.Fatalf("tc.db.Begin: %s", err)
|
||||
}
|
||||
defer rollbackOnPanic(tc.t, tx)
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
package ffldb
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/goleveldb/leveldb/iterator"
|
||||
"github.com/btcsuite/goleveldb/leveldb/util"
|
||||
"github.com/kaspanet/kaspad/database/internal/treap"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// ldbTreapIter wraps a treap iterator to provide the additional functionality
|
||||
|
||||
@@ -8,4 +8,4 @@ import (
|
||||
"github.com/kaspanet/kaspad/logger"
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.BCDB)
|
||||
var log, _ = logger.Get(logger.SubsystemTags.KSDB)
|
||||
|
||||
@@ -18,12 +18,12 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/goleveldb/leveldb"
|
||||
ldberrors "github.com/btcsuite/goleveldb/leveldb/errors"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
treap
|
||||
=====
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad/database/internal/treap)
|
||||
|
||||
Package treap implements a treap data structure that is used to hold ordered
|
||||
|
||||
@@ -6,24 +6,11 @@ package treap
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// fromHex converts the passed hex string into a byte slice and will panic if
|
||||
// there is an error. This is only provided for the hard-coded constants so
|
||||
// errors in the source code can be detected. It will only (and must only) be
|
||||
// called for initialization purposes.
|
||||
func fromHex(s string) []byte {
|
||||
r, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic("invalid hex in source file: " + s)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// serializeUint32 returns the big-endian encoding of the passed uint32.
|
||||
func serializeUint32(ui uint32) []byte {
|
||||
var ret [4]byte
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user