Compare commits

..

1 Commits

Author SHA1 Message Date
stasatdaglabs
5f3fb0bf9f [NOD-1238] Fix acceptance index never being initialized. (#859) 2020-08-11 12:04:54 +03:00
684 changed files with 36050 additions and 28487 deletions

View File

@@ -40,8 +40,10 @@ recommended that `GOPATH` is set to a directory in your home directory such as
```bash
$ git clone https://github.com/kaspanet/kaspad $GOPATH/src/github.com/kaspanet/kaspad
$ cd $GOPATH/src/github.com/kaspanet/kaspad
$ ./test.sh
$ go install . ./cmd/...
```
`./test.sh` tests can be skipped, but some things might not run correctly on your system if tests fail.
- Kaspad (and utilities) should now be installed in `$GOPATH/bin`. If you did
not already add the bin directory to your system path during Go installation,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,635 @@
// 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 addressmanager
import (
"fmt"
"io/ioutil"
"net"
"reflect"
"testing"
"time"
"github.com/kaspanet/kaspad/config"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/domainmessage"
)
// naTest is used to describe a test to be performed against the NetAddressKey
// method.
type naTest struct {
in domainmessage.NetAddress
want AddressKey
}
// naTests houses all of the tests to be performed against the NetAddressKey
// method.
var naTests = make([]naTest, 0)
// Put some IP in here for convenience. Points to google.
var someIP = "173.194.115.66"
// addNaTests
func addNaTests() {
// IPv4
// Localhost
addNaTest("127.0.0.1", 16111, "127.0.0.1:16111")
addNaTest("127.0.0.1", 16110, "127.0.0.1:16110")
// Class A
addNaTest("1.0.0.1", 16111, "1.0.0.1:16111")
addNaTest("2.2.2.2", 16110, "2.2.2.2:16110")
addNaTest("27.253.252.251", 8335, "27.253.252.251:8335")
addNaTest("123.3.2.1", 8336, "123.3.2.1:8336")
// Private Class A
addNaTest("10.0.0.1", 16111, "10.0.0.1:16111")
addNaTest("10.1.1.1", 16110, "10.1.1.1:16110")
addNaTest("10.2.2.2", 8335, "10.2.2.2:8335")
addNaTest("10.10.10.10", 8336, "10.10.10.10:8336")
// Class B
addNaTest("128.0.0.1", 16111, "128.0.0.1:16111")
addNaTest("129.1.1.1", 16110, "129.1.1.1:16110")
addNaTest("180.2.2.2", 8335, "180.2.2.2:8335")
addNaTest("191.10.10.10", 8336, "191.10.10.10:8336")
// Private Class B
addNaTest("172.16.0.1", 16111, "172.16.0.1:16111")
addNaTest("172.16.1.1", 16110, "172.16.1.1:16110")
addNaTest("172.16.2.2", 8335, "172.16.2.2:8335")
addNaTest("172.16.172.172", 8336, "172.16.172.172:8336")
// Class C
addNaTest("193.0.0.1", 16111, "193.0.0.1:16111")
addNaTest("200.1.1.1", 16110, "200.1.1.1:16110")
addNaTest("205.2.2.2", 8335, "205.2.2.2:8335")
addNaTest("223.10.10.10", 8336, "223.10.10.10:8336")
// Private Class C
addNaTest("192.168.0.1", 16111, "192.168.0.1:16111")
addNaTest("192.168.1.1", 16110, "192.168.1.1:16110")
addNaTest("192.168.2.2", 8335, "192.168.2.2:8335")
addNaTest("192.168.192.192", 8336, "192.168.192.192:8336")
// IPv6
// Localhost
addNaTest("::1", 16111, "[::1]:16111")
addNaTest("fe80::1", 16110, "[fe80::1]:16110")
// Link-local
addNaTest("fe80::1:1", 16111, "[fe80::1:1]:16111")
addNaTest("fe91::2:2", 16110, "[fe91::2:2]:16110")
addNaTest("fea2::3:3", 8335, "[fea2::3:3]:8335")
addNaTest("feb3::4:4", 8336, "[feb3::4:4]:8336")
// Site-local
addNaTest("fec0::1:1", 16111, "[fec0::1:1]:16111")
addNaTest("fed1::2:2", 16110, "[fed1::2:2]:16110")
addNaTest("fee2::3:3", 8335, "[fee2::3:3]:8335")
addNaTest("fef3::4:4", 8336, "[fef3::4:4]:8336")
}
func addNaTest(ip string, port uint16, want AddressKey) {
nip := net.ParseIP(ip)
na := *domainmessage.NewNetAddressIPPort(nip, port, domainmessage.SFNodeNetwork)
test := naTest{na, want}
naTests = append(naTests, test)
}
func lookupFuncForTest(host string) ([]net.IP, error) {
return nil, errors.New("not implemented")
}
func newAddrManagerForTest(t *testing.T, testName string,
localSubnetworkID *subnetworkid.SubnetworkID) (addressManager *AddressManager, teardown func()) {
cfg := config.DefaultConfig()
cfg.SubnetworkID = localSubnetworkID
dbPath, err := ioutil.TempDir("", testName)
if err != nil {
t.Fatalf("Error creating temporary directory: %s", err)
}
databaseContext, err := dbaccess.New(dbPath)
if err != nil {
t.Fatalf("error creating db: %s", err)
}
addressManager = New(cfg, databaseContext)
return addressManager, func() {
err := databaseContext.Close()
if err != nil {
t.Fatalf("error closing the database: %s", err)
}
}
}
func TestStartStop(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestStartStop", nil)
defer teardown()
err := amgr.Start()
if err != nil {
t.Fatalf("Address Manager failed to start: %v", err)
}
err = amgr.Stop()
if err != nil {
t.Fatalf("Address Manager failed to stop: %v", err)
}
}
func TestAddAddressByIP(t *testing.T) {
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, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
defer teardown()
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) {
var tests = []struct {
address domainmessage.NetAddress
priority AddressPriority
valid bool
}{
{
domainmessage.NetAddress{IP: net.ParseIP("192.168.0.100")},
InterfacePrio,
false,
},
{
domainmessage.NetAddress{IP: net.ParseIP("204.124.1.1")},
InterfacePrio,
true,
},
{
domainmessage.NetAddress{IP: net.ParseIP("204.124.1.1")},
BoundPrio,
true,
},
{
domainmessage.NetAddress{IP: net.ParseIP("::1")},
InterfacePrio,
false,
},
{
domainmessage.NetAddress{IP: net.ParseIP("fe80::1")},
InterfacePrio,
false,
},
{
domainmessage.NetAddress{IP: net.ParseIP("2620:100::1")},
InterfacePrio,
true,
},
}
amgr, teardown := newAddrManagerForTest(t, "TestAddLocalAddress", nil)
defer teardown()
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) {
amgr, teardown := newAddrManagerForTest(t, "TestAttempt", nil)
defer teardown()
// Add a new address and get it
err := amgr.AddAddressByIP(someIP+":8333", nil)
if err != nil {
t.Fatalf("Adding address failed: %v", err)
}
ka := amgr.GetAddress()
if !ka.LastAttempt().IsZero() {
t.Errorf("Address should not have attempts, but does")
}
na := ka.NetAddress()
amgr.Attempt(na)
if ka.LastAttempt().IsZero() {
t.Errorf("Address should have an attempt, but does not")
}
}
func TestConnected(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestConnected", nil)
defer teardown()
// Add a new address and get it
err := amgr.AddAddressByIP(someIP+":8333", nil)
if err != nil {
t.Fatalf("Adding address failed: %v", err)
}
ka := amgr.GetAddress()
na := ka.NetAddress()
// make it an hour ago
na.Timestamp = mstime.Now().Add(time.Hour * -1)
amgr.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) {
amgr, teardown := newAddrManagerForTest(t, "TestNeedMoreAddresses", nil)
defer teardown()
addrsToAdd := 1500
b := amgr.NeedMoreAddresses()
if !b {
t.Errorf("Expected that we need more addresses")
}
addrs := make([]*domainmessage.NetAddress, addrsToAdd)
var err error
for i := 0; i < addrsToAdd; i++ {
s := AddressKey(fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60))
addrs[i], err = amgr.DeserializeNetAddress(s)
if err != nil {
t.Errorf("Failed to turn %s into an address: %v", s, err)
}
}
srcAddr := domainmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
amgr.AddAddresses(addrs, srcAddr, nil)
numAddrs := amgr.TotalNumAddresses()
if numAddrs > addrsToAdd {
t.Errorf("Number of addresses is too many %d vs %d", numAddrs, addrsToAdd)
}
b = amgr.NeedMoreAddresses()
if b {
t.Errorf("Expected that we don't need more addresses")
}
}
func TestGood(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestGood", nil)
defer teardown()
addrsToAdd := 64 * 64
addrs := make([]*domainmessage.NetAddress, addrsToAdd)
subnetworkCount := 32
subnetworkIDs := make([]*subnetworkid.SubnetworkID, subnetworkCount)
var err error
for i := 0; i < addrsToAdd; i++ {
s := AddressKey(fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60))
addrs[i], err = amgr.DeserializeNetAddress(s)
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 := domainmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
amgr.AddAddresses(addrs, srcAddr, nil)
for i, addr := range addrs {
amgr.Good(addr, subnetworkIDs[i%subnetworkCount])
}
numAddrs := amgr.TotalNumAddresses()
if numAddrs >= addrsToAdd {
t.Errorf("Number of addresses is too many: %d vs %d", numAddrs, addrsToAdd)
}
numCache := len(amgr.AddressCache(true, nil))
if numCache == 0 || numCache >= numAddrs/4 {
t.Errorf("Number of addresses in cache: got %d, want positive and less than %d",
numCache, numAddrs/4)
}
for i := 0; i < subnetworkCount; i++ {
numCache = len(amgr.AddressCache(false, subnetworkIDs[i]))
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) {
amgr, teardown := newAddrManagerForTest(t, "TestGoodChangeSubnetworkID", nil)
defer teardown()
addr := domainmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
addrKey := NetAddressKey(addr)
srcAddr := domainmessage.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
oldSubnetwork := subnetworkid.SubnetworkIDNative
amgr.AddAddress(addr, srcAddr, oldSubnetwork)
amgr.Good(addr, oldSubnetwork)
// make sure address was saved to addressIndex under oldSubnetwork
ka := amgr.knownAddress(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 := amgr.subnetworkTriedAddresBucketArrays[*oldSubnetwork][amgr.triedAddressBucketIndex(addr)]
wasFound := false
for _, ka := range bucket {
if NetAddressKey(ka.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
amgr.Good(addr, newSubnetwork)
// make sure address was updated in addressIndex under newSubnetwork
ka = amgr.knownAddress(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 = amgr.subnetworkTriedAddresBucketArrays[*oldSubnetwork][amgr.triedAddressBucketIndex(addr)]
wasFound = false
for _, ka := range bucket {
if NetAddressKey(ka.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 = amgr.subnetworkTriedAddresBucketArrays[*newSubnetwork][amgr.triedAddressBucketIndex(addr)]
wasFound = false
for _, ka := range bucket {
if NetAddressKey(ka.NetAddress()) == addrKey {
wasFound = true
}
}
if !wasFound {
t.Fatalf("Address was not found in the correct bucket in newSubnetwork")
}
}
func TestGetAddress(t *testing.T) {
localSubnetworkID := &subnetworkid.SubnetworkID{0xff}
amgr, teardown := newAddrManagerForTest(t, "TestGetAddress", localSubnetworkID)
defer teardown()
// Get an address from an empty set (should error)
if rv := amgr.GetAddress(); rv != nil {
t.Errorf("GetAddress failed: got: %v want: %v\n", rv, nil)
}
// Add a new address and get it
err := amgr.AddAddressByIP(someIP+":8332", localSubnetworkID)
if err != nil {
t.Fatalf("Adding address failed: %v", err)
}
ka := amgr.GetAddress()
if ka == nil {
t.Fatalf("Did not get an address where there is one in the pool")
}
amgr.Attempt(ka.NetAddress())
// Checks that we don't get it if we find that it has other subnetwork ID than expected.
actualSubnetworkID := &subnetworkid.SubnetworkID{0xfe}
amgr.Good(ka.NetAddress(), actualSubnetworkID)
ka = amgr.GetAddress()
if ka != nil {
t.Errorf("Didn't expect to get an address because there shouldn't be any address from subnetwork ID %s or nil", localSubnetworkID)
}
// Checks that the total number of addresses incremented although the new address is not full node or a partial node of the same subnetwork as the local node.
numAddrs := amgr.TotalNumAddresses()
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 = amgr.AddAddressByIP(someIP+":8333", localSubnetworkID)
if err != nil {
t.Fatalf("Adding address failed: %v", err)
}
ka = amgr.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)
}
amgr.Attempt(ka.NetAddress())
// Mark this as a good address and get it
amgr.Good(ka.NetAddress(), localSubnetworkID)
ka = amgr.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 = amgr.TotalNumAddresses()
if numAddrs != 2 {
t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1)
}
}
func TestGetBestLocalAddress(t *testing.T) {
localAddrs := []domainmessage.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 domainmessage.NetAddress
want0 domainmessage.NetAddress
want1 domainmessage.NetAddress
want2 domainmessage.NetAddress
want3 domainmessage.NetAddress
}{
{
// Remote connection from public IPv4
domainmessage.NetAddress{IP: net.ParseIP("204.124.8.1")},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.ParseIP("204.124.8.100")},
domainmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
},
{
// Remote connection from private IPv4
domainmessage.NetAddress{IP: net.ParseIP("172.16.0.254")},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.IPv4zero},
},
{
// Remote connection from public IPv6
domainmessage.NetAddress{IP: net.ParseIP("2602:100:abcd::102")},
domainmessage.NetAddress{IP: net.IPv6zero},
domainmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
domainmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
domainmessage.NetAddress{IP: net.ParseIP("2001:470::1")},
},
/* XXX
{
// Remote connection from Tor
domainmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43::100")},
domainmessage.NetAddress{IP: net.IPv4zero},
domainmessage.NetAddress{IP: net.ParseIP("204.124.8.100")},
domainmessage.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
},
*/
}
amgr, teardown := newAddrManagerForTest(t, "TestGetBestLocalAddress", nil)
defer teardown()
// 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 := domainmessage.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 = domainmessage.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()
t.Logf("Running %d tests", len(naTests))
for i, test := range naTests {
key := NetAddressKey(&test.in)
if key != test.want {
t.Errorf("NetAddressKey #%d\n got: %s want: %s", i, key, test.want)
continue
}
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package addressmanager
import (
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/util/mstime"
)
func TstKnownAddressIsBad(ka *KnownAddress) bool {
return ka.isBad()
}
func TstKnownAddressChance(ka *KnownAddress) float64 {
return ka.chance()
}
func TstNewKnownAddress(na *domainmessage.NetAddress, attempts int,
lastattempt, lastsuccess mstime.Time, tried bool, refs int) *KnownAddress {
return &KnownAddress{netAddress: na, attempts: attempts, lastAttempt: lastattempt,
lastSuccess: lastsuccess, tried: tried, referenceCount: refs}
}

View File

@@ -0,0 +1,108 @@
// 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 addressmanager
import (
"github.com/kaspanet/kaspad/util/mstime"
"time"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/domainmessage"
)
// KnownAddress tracks information about a known network address that is used
// to determine how viable an address is.
type KnownAddress struct {
netAddress *domainmessage.NetAddress
sourceAddress *domainmessage.NetAddress
attempts int
lastAttempt mstime.Time
lastSuccess mstime.Time
tried bool
referenceCount int // reference count of new buckets
subnetworkID *subnetworkid.SubnetworkID
isBanned bool
bannedTime mstime.Time
}
// NetAddress returns the underlying domainmessage.NetAddress associated with the
// known address.
func (ka *KnownAddress) NetAddress() *domainmessage.NetAddress {
return ka.netAddress
}
// SubnetworkID returns the subnetwork ID of the known address.
func (ka *KnownAddress) SubnetworkID() *subnetworkid.SubnetworkID {
return ka.subnetworkID
}
// LastAttempt returns the last time the known address was attempted.
func (ka *KnownAddress) LastAttempt() mstime.Time {
return ka.lastAttempt
}
// chance returns the selection probability for a known address. The priority
// depends upon how recently the address has been seen, how recently it was last
// attempted and how often attempts to connect to it have failed.
func (ka *KnownAddress) chance() float64 {
now := mstime.Now()
lastAttempt := now.Sub(ka.lastAttempt)
if lastAttempt < 0 {
lastAttempt = 0
}
c := 1.0
// Very recent attempts are less likely to be retried.
if lastAttempt < 10*time.Minute {
c *= 0.01
}
// Failed attempts deprioritise.
for i := ka.attempts; i > 0; i-- {
c /= 1.5
}
return c
}
// isBad returns true if the address in question has not been tried in the last
// minute and meets one of the following criteria:
// 1) It claims to be from the future
// 2) It hasn't been seen in over a month
// 3) It has failed at least three times and never succeeded
// 4) It has failed ten times in the last week
// All addresses that meet these criteria are assumed to be worthless and not
// worth keeping hold of.
func (ka *KnownAddress) isBad() bool {
if ka.lastAttempt.After(mstime.Now().Add(-1 * time.Minute)) {
return false
}
// From the future?
if ka.netAddress.Timestamp.After(mstime.Now().Add(10 * time.Minute)) {
return true
}
// Over a month old?
if ka.netAddress.Timestamp.Before(mstime.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
return true
}
// Never succeeded?
if ka.lastSuccess.IsZero() && ka.attempts >= numRetries {
return true
}
// Hasn't succeeded in too long?
if !ka.lastSuccess.After(mstime.Now().Add(-1*minBadDays*time.Hour*24)) &&
ka.attempts >= maxFailures {
return true
}
return false
}

View File

@@ -0,0 +1,115 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package addressmanager_test
import (
"github.com/kaspanet/kaspad/util/mstime"
"math"
"testing"
"time"
"github.com/kaspanet/kaspad/addressmanager"
"github.com/kaspanet/kaspad/domainmessage"
)
func TestChance(t *testing.T) {
now := mstime.Now()
var tests = []struct {
addr *addressmanager.KnownAddress
expected float64
}{
{
//Test normal case
addressmanager.TstNewKnownAddress(&domainmessage.NetAddress{Timestamp: now.Add(-35 * time.Second)},
0, mstime.Now().Add(-30*time.Minute), mstime.Now(), false, 0),
1.0,
}, {
//Test case in which lastseen < 0
addressmanager.TstNewKnownAddress(&domainmessage.NetAddress{Timestamp: now.Add(20 * time.Second)},
0, mstime.Now().Add(-30*time.Minute), mstime.Now(), false, 0),
1.0,
}, {
//Test case in which lastAttempt < 0
addressmanager.TstNewKnownAddress(&domainmessage.NetAddress{Timestamp: now.Add(-35 * time.Second)},
0, mstime.Now().Add(30*time.Minute), mstime.Now(), false, 0),
1.0 * .01,
}, {
//Test case in which lastAttempt < ten minutes
addressmanager.TstNewKnownAddress(&domainmessage.NetAddress{Timestamp: now.Add(-35 * time.Second)},
0, mstime.Now().Add(-5*time.Minute), mstime.Now(), false, 0),
1.0 * .01,
}, {
//Test case with several failed attempts.
addressmanager.TstNewKnownAddress(&domainmessage.NetAddress{Timestamp: now.Add(-35 * time.Second)},
2, mstime.Now().Add(-30*time.Minute), mstime.Now(), false, 0),
1 / 1.5 / 1.5,
},
}
err := .0001
for i, test := range tests {
chance := addressmanager.TstKnownAddressChance(test.addr)
if math.Abs(test.expected-chance) >= err {
t.Errorf("case %d: got %f, expected %f", i, chance, test.expected)
}
}
}
func TestIsBad(t *testing.T) {
now := mstime.Now()
future := now.Add(35 * time.Minute)
monthOld := now.Add(-43 * time.Hour * 24)
secondsOld := now.Add(-2 * time.Second)
minutesOld := now.Add(-27 * time.Minute)
hoursOld := now.Add(-5 * time.Hour)
zeroTime := mstime.Time{}
futureNa := &domainmessage.NetAddress{Timestamp: future}
minutesOldNa := &domainmessage.NetAddress{Timestamp: minutesOld}
monthOldNa := &domainmessage.NetAddress{Timestamp: monthOld}
currentNa := &domainmessage.NetAddress{Timestamp: secondsOld}
//Test addresses that have been tried in the last minute.
if addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(futureNa, 3, secondsOld, zeroTime, false, 0)) {
t.Errorf("test case 1: addresses that have been tried in the last minute are not bad.")
}
if addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(monthOldNa, 3, secondsOld, zeroTime, false, 0)) {
t.Errorf("test case 2: addresses that have been tried in the last minute are not bad.")
}
if addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(currentNa, 3, secondsOld, zeroTime, false, 0)) {
t.Errorf("test case 3: addresses that have been tried in the last minute are not bad.")
}
if addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(currentNa, 3, secondsOld, monthOld, true, 0)) {
t.Errorf("test case 4: addresses that have been tried in the last minute are not bad.")
}
if addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(currentNa, 2, secondsOld, secondsOld, true, 0)) {
t.Errorf("test case 5: addresses that have been tried in the last minute are not bad.")
}
//Test address that claims to be from the future.
if !addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(futureNa, 0, minutesOld, hoursOld, true, 0)) {
t.Errorf("test case 6: addresses that claim to be from the future are bad.")
}
//Test address that has not been seen in over a month.
if !addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(monthOldNa, 0, minutesOld, hoursOld, true, 0)) {
t.Errorf("test case 7: addresses more than a month old are bad.")
}
//It has failed at least three times and never succeeded.
if !addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(minutesOldNa, 3, minutesOld, zeroTime, true, 0)) {
t.Errorf("test case 8: addresses that have never succeeded are bad.")
}
//It has failed ten times in the last week
if !addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(minutesOldNa, 10, minutesOld, monthOld, true, 0)) {
t.Errorf("test case 9: addresses that have not succeeded in too long are bad.")
}
//Test an address that should work.
if addressmanager.TstKnownAddressIsBad(addressmanager.TstNewKnownAddress(minutesOldNa, 2, minutesOld, hoursOld, true, 0)) {
t.Errorf("test case 10: This should be a valid address.")
}
}

View File

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

View File

@@ -7,7 +7,7 @@ package addressmanager
import (
"net"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domainmessage"
)
var (
@@ -78,13 +78,6 @@ var (
heNet = ipNet("2001:470::", 32, 128)
)
const (
// GetAddressesMax is the most addresses that we will send in response
// to a getAddress (in practise the most addresses we will return from a
// call to AddressCache()).
GetAddressesMax = 2500
)
// ipNet returns a net.IPNet struct given the passed IP address string, number
// of one bits to include at the start of the mask, and the total number of bits
// for the mask.
@@ -93,19 +86,19 @@ func ipNet(ip string, ones, bits int) net.IPNet {
}
// IsIPv4 returns whether or not the given address is an IPv4 address.
func IsIPv4(na *appmessage.NetAddress) bool {
func IsIPv4(na *domainmessage.NetAddress) bool {
return na.IP.To4() != nil
}
// IsLocal returns whether or not the given address is a local address.
func IsLocal(na *appmessage.NetAddress) bool {
func IsLocal(na *domainmessage.NetAddress) bool {
return na.IP.IsLoopback() || zero4Net.Contains(na.IP)
}
// IsRFC1918 returns whether or not the passed address is part of the IPv4
// private network address space as defined by RFC1918 (10.0.0.0/8,
// 172.16.0.0/12, or 192.168.0.0/16).
func IsRFC1918(na *appmessage.NetAddress) bool {
func IsRFC1918(na *domainmessage.NetAddress) bool {
for _, rfc := range rfc1918Nets {
if rfc.Contains(na.IP) {
return true
@@ -116,56 +109,56 @@ func IsRFC1918(na *appmessage.NetAddress) bool {
// IsRFC2544 returns whether or not the passed address is part of the IPv4
// address space as defined by RFC2544 (198.18.0.0/15)
func IsRFC2544(na *appmessage.NetAddress) bool {
func IsRFC2544(na *domainmessage.NetAddress) bool {
return rfc2544Net.Contains(na.IP)
}
// IsRFC3849 returns whether or not the passed address is part of the IPv6
// documentation range as defined by RFC3849 (2001:DB8::/32).
func IsRFC3849(na *appmessage.NetAddress) bool {
func IsRFC3849(na *domainmessage.NetAddress) bool {
return rfc3849Net.Contains(na.IP)
}
// IsRFC3927 returns whether or not the passed address is part of the IPv4
// autoconfiguration range as defined by RFC3927 (169.254.0.0/16).
func IsRFC3927(na *appmessage.NetAddress) bool {
func IsRFC3927(na *domainmessage.NetAddress) bool {
return rfc3927Net.Contains(na.IP)
}
// IsRFC3964 returns whether or not the passed address is part of the IPv6 to
// IPv4 encapsulation range as defined by RFC3964 (2002::/16).
func IsRFC3964(na *appmessage.NetAddress) bool {
func IsRFC3964(na *domainmessage.NetAddress) bool {
return rfc3964Net.Contains(na.IP)
}
// IsRFC4193 returns whether or not the passed address is part of the IPv6
// unique local range as defined by RFC4193 (FC00::/7).
func IsRFC4193(na *appmessage.NetAddress) bool {
func IsRFC4193(na *domainmessage.NetAddress) bool {
return rfc4193Net.Contains(na.IP)
}
// IsRFC4380 returns whether or not the passed address is part of the IPv6
// teredo tunneling over UDP range as defined by RFC4380 (2001::/32).
func IsRFC4380(na *appmessage.NetAddress) bool {
func IsRFC4380(na *domainmessage.NetAddress) bool {
return rfc4380Net.Contains(na.IP)
}
// IsRFC4843 returns whether or not the passed address is part of the IPv6
// ORCHID range as defined by RFC4843 (2001:10::/28).
func IsRFC4843(na *appmessage.NetAddress) bool {
func IsRFC4843(na *domainmessage.NetAddress) bool {
return rfc4843Net.Contains(na.IP)
}
// IsRFC4862 returns whether or not the passed address is part of the IPv6
// stateless address autoconfiguration range as defined by RFC4862 (FE80::/64).
func IsRFC4862(na *appmessage.NetAddress) bool {
func IsRFC4862(na *domainmessage.NetAddress) bool {
return rfc4862Net.Contains(na.IP)
}
// IsRFC5737 returns whether or not the passed address is part of the IPv4
// documentation address space as defined by RFC5737 (192.0.2.0/24,
// 198.51.100.0/24, 203.0.113.0/24)
func IsRFC5737(na *appmessage.NetAddress) bool {
func IsRFC5737(na *domainmessage.NetAddress) bool {
for _, rfc := range rfc5737Net {
if rfc.Contains(na.IP) {
return true
@@ -177,19 +170,19 @@ func IsRFC5737(na *appmessage.NetAddress) bool {
// IsRFC6052 returns whether or not the passed address is part of the IPv6
// well-known prefix range as defined by RFC6052 (64:FF9B::/96).
func IsRFC6052(na *appmessage.NetAddress) bool {
func IsRFC6052(na *domainmessage.NetAddress) bool {
return rfc6052Net.Contains(na.IP)
}
// IsRFC6145 returns whether or not the passed address is part of the IPv6 to
// IPv4 translated address range as defined by RFC6145 (::FFFF:0:0:0/96).
func IsRFC6145(na *appmessage.NetAddress) bool {
func IsRFC6145(na *domainmessage.NetAddress) bool {
return rfc6145Net.Contains(na.IP)
}
// IsRFC6598 returns whether or not the passed address is part of the IPv4
// shared address space specified by RFC6598 (100.64.0.0/10)
func IsRFC6598(na *appmessage.NetAddress) bool {
func IsRFC6598(na *domainmessage.NetAddress) bool {
return rfc6598Net.Contains(na.IP)
}
@@ -197,7 +190,7 @@ func IsRFC6598(na *appmessage.NetAddress) bool {
// considered invalid under the following circumstances:
// IPv4: It is either a zero or all bits set address.
// IPv6: It is either a zero or RFC3849 documentation address.
func IsValid(na *appmessage.NetAddress) bool {
func IsValid(na *domainmessage.NetAddress) bool {
// IsUnspecified returns if address is 0, so only all bits set, and
// RFC3849 need to be explicitly checked.
return na.IP != nil && !(na.IP.IsUnspecified() ||
@@ -207,8 +200,8 @@ func IsValid(na *appmessage.NetAddress) bool {
// IsRoutable returns whether or not the passed address is routable over
// the public internet. This is true as long as the address is valid and is not
// in any reserved ranges.
func IsRoutable(na *appmessage.NetAddress, acceptUnroutable bool) bool {
if acceptUnroutable {
func (am *AddressManager) IsRoutable(na *domainmessage.NetAddress) bool {
if am.cfg.NetParams().AcceptUnroutable {
return !IsLocal(na)
}
@@ -222,11 +215,11 @@ func IsRoutable(na *appmessage.NetAddress, acceptUnroutable bool) bool {
// of. This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string
// "local" for a local address, and the string "unroutable" for an unroutable
// address.
func (am *AddressManager) GroupKey(na *appmessage.NetAddress) string {
func (am *AddressManager) GroupKey(na *domainmessage.NetAddress) string {
if IsLocal(na) {
return "local"
}
if !IsRoutable(na, am.cfg.AcceptUnroutable) {
if !am.IsRoutable(na) {
return "unroutable"
}
if IsIPv4(na) {

View File

@@ -8,16 +8,16 @@ import (
"net"
"testing"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domainmessage"
)
// TestIPTypes ensures the various functions which determine the type of an IP
// address based on RFCs work as intended.
func TestIPTypes(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP")
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
defer teardown()
type ipTest struct {
in appmessage.NetAddress
in domainmessage.NetAddress
rfc1918 bool
rfc2544 bool
rfc3849 bool
@@ -40,7 +40,7 @@ func TestIPTypes(t *testing.T) {
rfc4193, rfc4380, rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598,
local, valid, routable bool) ipTest {
nip := net.ParseIP(ip)
na := *appmessage.NewNetAddressIPPort(nip, 16111, appmessage.SFNodeNetwork)
na := *domainmessage.NewNetAddressIPPort(nip, 16111, domainmessage.SFNodeNetwork)
test := ipTest{na, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380,
rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, local, valid, routable}
return test
@@ -137,7 +137,7 @@ func TestIPTypes(t *testing.T) {
t.Errorf("IsValid %s\n got: %v want: %v", test.in.IP, rv, test.valid)
}
if rv := IsRoutable(&test.in, amgr.cfg.AcceptUnroutable); rv != test.routable {
if rv := amgr.IsRoutable(&test.in); rv != test.routable {
t.Errorf("IsRoutable %s\n got: %v want: %v", test.in.IP, rv, test.routable)
}
}
@@ -146,7 +146,7 @@ func TestIPTypes(t *testing.T) {
// TestGroupKey tests the GroupKey function to ensure it properly groups various
// IP addresses.
func TestGroupKey(t *testing.T) {
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP")
amgr, teardown := newAddrManagerForTest(t, "TestAddAddressByIP", nil)
defer teardown()
tests := []struct {
@@ -196,7 +196,7 @@ func TestGroupKey(t *testing.T) {
for i, test := range tests {
nip := net.ParseIP(test.ip)
na := *appmessage.NewNetAddressIPPort(nip, 8333, appmessage.SFNodeNetwork)
na := *domainmessage.NewNetAddressIPPort(nip, 8333, domainmessage.SFNodeNetwork)
if key := amgr.GroupKey(&na); key != test.expected {
t.Errorf("TestGroupKey #%d (%s): unexpected group key "+
"- got '%s', want '%s'", i, test.name,

View File

@@ -2,194 +2,248 @@ package app
import (
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"sync/atomic"
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
"github.com/kaspanet/kaspad/addressmanager"
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
"github.com/kaspanet/kaspad/infrastructure/os/signal"
"github.com/kaspanet/kaspad/util/profiling"
"github.com/kaspanet/kaspad/version"
"github.com/kaspanet/kaspad/netadapter/id"
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/blockdag/indexers"
"github.com/kaspanet/kaspad/config"
"github.com/kaspanet/kaspad/connmanager"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/dnsseed"
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/mempool"
"github.com/kaspanet/kaspad/mining"
"github.com/kaspanet/kaspad/netadapter"
"github.com/kaspanet/kaspad/protocol"
"github.com/kaspanet/kaspad/rpc"
"github.com/kaspanet/kaspad/signal"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/panics"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/infrastructure/os/execenv"
"github.com/kaspanet/kaspad/infrastructure/os/limits"
"github.com/kaspanet/kaspad/infrastructure/os/winservice"
)
var desiredLimits = &limits.DesiredLimits{
FileLimitWant: 2048,
FileLimitMin: 1024,
// App is a wrapper for all the kaspad services
type App struct {
cfg *config.Config
rpcServer *rpc.Server
addressManager *addressmanager.AddressManager
protocolManager *protocol.Manager
connectionManager *connmanager.ConnectionManager
netAdapter *netadapter.NetAdapter
started, shutdown int32
}
var serviceDescription = &winservice.ServiceDescription{
Name: "kaspadsvc",
DisplayName: "Kaspad Service",
Description: "Downloads and stays synchronized with the Kaspa blockDAG and " +
"provides DAG services to applications.",
}
// Start launches all the kaspad services.
func (a *App) Start() {
// Already started?
if atomic.AddInt32(&a.started, 1) != 1 {
return
}
type kaspadApp struct {
cfg *config.Config
}
log.Trace("Starting kaspad")
// StartApp starts the kaspad app, and blocks until it finishes running
func StartApp() error {
execenv.Initialize(desiredLimits)
// Load configuration and parse command line. This function also
// initializes logging and configures it accordingly.
cfg, err := config.LoadConfig()
err := a.protocolManager.Start()
if err != nil {
fmt.Fprint(os.Stderr, err)
return err
}
defer panics.HandlePanic(log, "MAIN", nil)
app := &kaspadApp{cfg: cfg}
// Call serviceMain on Windows to handle running as a service. When
// the return isService flag is true, exit now since we ran as a
// service. Otherwise, just fall through to normal operation.
if runtime.GOOS == "windows" {
isService, err := winservice.WinServiceMain(app.main, serviceDescription, cfg)
if err != nil {
return err
}
if isService {
return nil
}
panics.Exit(log, fmt.Sprintf("Error starting the p2p protocol: %+v", err))
}
return app.main(nil)
a.maybeSeedFromDNS()
a.connectionManager.Start()
if !a.cfg.DisableRPC {
a.rpcServer.Start()
}
}
func (app *kaspadApp) main(startedChan chan<- struct{}) error {
// Get a channel that will be closed when a shutdown signal has been
// triggered either from an OS signal such as SIGINT (Ctrl+C) or from
// another subsystem such as the RPC server.
interrupt := signal.InterruptListener()
defer log.Info("Shutdown complete")
// Show version at startup.
log.Infof("Version %s", version.Version())
// Enable http profiling server if requested.
if app.cfg.Profile != "" {
profiling.Start(app.cfg.Profile, log)
}
// Perform upgrades to kaspad as new versions require it.
if err := doUpgrades(); err != nil {
log.Error(err)
return err
}
// Return now if an interrupt signal was triggered.
if signal.InterruptRequested(interrupt) {
// Stop gracefully shuts down all the kaspad services.
func (a *App) Stop() error {
// Make sure this only happens once.
if atomic.AddInt32(&a.shutdown, 1) != 1 {
log.Infof("Kaspad is already in the process of shutting down")
return nil
}
if app.cfg.ResetDatabase {
err := removeDatabase(app.cfg)
if err != nil {
log.Error(err)
return err
}
}
log.Warnf("Kaspad shutting down")
// Open the database
databaseContext, err := openDB(app.cfg)
a.connectionManager.Stop()
err := a.protocolManager.Stop()
if err != nil {
log.Error(err)
return err
log.Errorf("Error stopping the p2p protocol: %+v", err)
}
defer func() {
log.Infof("Gracefully shutting down the database...")
err := databaseContext.Close()
// Shutdown the RPC server if it's not disabled.
if !a.cfg.DisableRPC {
err := a.rpcServer.Stop()
if err != nil {
log.Errorf("Failed to close the database: %s", err)
log.Errorf("Error stopping rpcServer: %+v", err)
}
}()
// Return now if an interrupt signal was triggered.
if signal.InterruptRequested(interrupt) {
return nil
}
// Drop indexes and exit if requested.
if app.cfg.DropAcceptanceIndex {
if err := indexers.DropAcceptanceIndex(databaseContext); err != nil {
log.Errorf("%s", err)
return err
}
return nil
}
// Create componentManager and start it.
componentManager, err := NewComponentManager(app.cfg, databaseContext, interrupt)
if err != nil {
log.Errorf("Unable to start kaspad: %+v", err)
return err
}
defer func() {
log.Infof("Gracefully shutting down kaspad...")
shutdownDone := make(chan struct{})
go func() {
componentManager.Stop()
shutdownDone <- struct{}{}
}()
const shutdownTimeout = 2 * time.Minute
select {
case <-shutdownDone:
case <-time.After(shutdownTimeout):
log.Criticalf("Graceful shutdown timed out %s. Terminating...", shutdownTimeout)
}
log.Infof("Kaspad shutdown complete")
}()
componentManager.Start()
if startedChan != nil {
startedChan <- struct{}{}
}
// Wait until the interrupt signal is received from an OS signal or
// shutdown is requested through one of the subsystems such as the RPC
// server.
<-interrupt
return nil
}
// doUpgrades performs upgrades to kaspad as new versions require it.
// currently it's a placeholder we got from kaspad upstream, that does nothing
func doUpgrades() error {
return nil
// New returns a new App instance configured to listen on addr for the
// kaspa network type specified by dagParams. Use start to begin accepting
// connections from peers.
func New(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt <-chan struct{}) (*App, error) {
indexManager, acceptanceIndex := setupIndexes(cfg)
sigCache := txscript.NewSigCache(cfg.SigCacheMaxSize)
// Create a new block DAG instance with the appropriate configuration.
dag, err := setupDAG(cfg, databaseContext, interrupt, sigCache, indexManager)
if err != nil {
return nil, err
}
txMempool := setupMempool(cfg, dag, sigCache)
netAdapter, err := netadapter.NewNetAdapter(cfg)
if err != nil {
return nil, err
}
addressManager := addressmanager.New(cfg, databaseContext)
connectionManager, err := connmanager.New(cfg, netAdapter, addressManager)
if err != nil {
return nil, err
}
protocolManager, err := protocol.NewManager(cfg, dag, netAdapter, addressManager, txMempool, connectionManager)
if err != nil {
return nil, err
}
rpcServer, err := setupRPC(
cfg, dag, txMempool, sigCache, acceptanceIndex, connectionManager, addressManager, protocolManager)
if err != nil {
return nil, err
}
return &App{
cfg: cfg,
rpcServer: rpcServer,
protocolManager: protocolManager,
connectionManager: connectionManager,
netAdapter: netAdapter,
addressManager: addressManager,
}, nil
}
// dbPath returns the path to the block database given a database type.
func databasePath(cfg *config.Config) string {
return filepath.Join(cfg.DataDir, "db")
func (a *App) maybeSeedFromDNS() {
if !a.cfg.DisableDNSSeed {
dnsseed.SeedFromDNS(a.cfg.NetParams(), a.cfg.DNSSeed, domainmessage.SFNodeNetwork, false, nil,
a.cfg.Lookup, func(addresses []*domainmessage.NetAddress) {
// Kaspad uses a lookup of the dns seeder here. Since seeder returns
// IPs of nodes and not its own IP, we can not know real IP of
// source. So we'll take first returned address as source.
a.addressManager.AddAddresses(addresses, addresses[0], nil)
})
}
}
func setupDAG(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt <-chan struct{},
sigCache *txscript.SigCache, indexManager blockdag.IndexManager) (*blockdag.BlockDAG, error) {
dag, err := blockdag.New(&blockdag.Config{
Interrupt: interrupt,
DatabaseContext: databaseContext,
DAGParams: cfg.NetParams(),
TimeSource: blockdag.NewTimeSource(),
SigCache: sigCache,
IndexManager: indexManager,
SubnetworkID: cfg.SubnetworkID,
})
return dag, err
}
func removeDatabase(cfg *config.Config) error {
dbPath := databasePath(cfg)
return os.RemoveAll(dbPath)
func setupIndexes(cfg *config.Config) (blockdag.IndexManager, *indexers.AcceptanceIndex) {
// Create indexes if needed.
var indexes []indexers.Indexer
var acceptanceIndex *indexers.AcceptanceIndex
if cfg.AcceptanceIndex {
log.Info("acceptance index is enabled")
acceptanceIndex = indexers.NewAcceptanceIndex()
indexes = append(indexes, acceptanceIndex)
}
// Create an index manager if any of the optional indexes are enabled.
if len(indexes) < 0 {
return nil, nil
}
indexManager := indexers.NewManager(indexes)
return indexManager, acceptanceIndex
}
func openDB(cfg *config.Config) (*dbaccess.DatabaseContext, error) {
dbPath := databasePath(cfg)
log.Infof("Loading database from '%s'", dbPath)
return dbaccess.New(dbPath)
func setupMempool(cfg *config.Config, dag *blockdag.BlockDAG, sigCache *txscript.SigCache) *mempool.TxPool {
mempoolConfig := mempool.Config{
Policy: mempool.Policy{
AcceptNonStd: cfg.RelayNonStd,
MaxOrphanTxs: cfg.MaxOrphanTxs,
MaxOrphanTxSize: config.DefaultMaxOrphanTxSize,
MinRelayTxFee: cfg.MinRelayTxFee,
MaxTxVersion: 1,
},
CalcSequenceLockNoLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
return dag.CalcSequenceLockNoLock(tx, utxoSet, true)
},
IsDeploymentActive: dag.IsDeploymentActive,
SigCache: sigCache,
DAG: dag,
}
return mempool.New(&mempoolConfig)
}
func setupRPC(cfg *config.Config,
dag *blockdag.BlockDAG,
txMempool *mempool.TxPool,
sigCache *txscript.SigCache,
acceptanceIndex *indexers.AcceptanceIndex,
connectionManager *connmanager.ConnectionManager,
addressManager *addressmanager.AddressManager,
protocolManager *protocol.Manager) (*rpc.Server, error) {
if !cfg.DisableRPC {
policy := mining.Policy{
BlockMaxMass: cfg.BlockMaxMass,
}
blockTemplateGenerator := mining.NewBlkTmplGenerator(&policy, txMempool, dag, sigCache)
rpcServer, err := rpc.NewRPCServer(cfg, dag, txMempool, acceptanceIndex, blockTemplateGenerator,
connectionManager, addressManager, protocolManager)
if err != nil {
return nil, err
}
// Signal process shutdown when the RPC server requests it.
spawn("setupRPC-handleShutdownRequest", func() {
<-rpcServer.RequestedProcessShutdown()
signal.ShutdownRequestChannel <- struct{}{}
})
return rpcServer, nil
}
return nil, nil
}
// P2PNodeID returns the network ID associated with this App
func (a *App) P2PNodeID() *id.ID {
return a.netAdapter.ID()
}
// AddressManager returns the AddressManager associated with this App
func (a *App) AddressManager() *addressmanager.AddressManager {
return a.addressManager
}
// WaitForShutdown blocks until the main listener and peer handlers are stopped.
func (a *App) WaitForShutdown() {
// TODO(libp2p)
// a.p2pServer.WaitForShutdown()
}

View File

@@ -1,24 +0,0 @@
package appmessage
import "time"
type baseMessage struct {
messageNumber uint64
receivedAt time.Time
}
func (b *baseMessage) MessageNumber() uint64 {
return b.messageNumber
}
func (b *baseMessage) SetMessageNumber(messageNumber uint64) {
b.messageNumber = messageNumber
}
func (b *baseMessage) ReceivedAt() time.Time {
return b.receivedAt
}
func (b *baseMessage) SetReceivedAt(receivedAt time.Time) {
b.receivedAt = receivedAt
}

View File

@@ -1,195 +0,0 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package appmessage
import (
"fmt"
"time"
)
// MaxMessagePayload is the maximum bytes a message can be regardless of other
// individual limits imposed by messages themselves.
const MaxMessagePayload = 1024 * 1024 * 32 // 32MB
// MessageCommand is a number in the header of a message that represents its type.
type MessageCommand uint32
func (cmd MessageCommand) String() string {
cmdString, ok := ProtocolMessageCommandToString[cmd]
if !ok {
cmdString, ok = RPCMessageCommandToString[cmd]
}
if !ok {
cmdString = "unknown command"
}
return fmt.Sprintf("%s [code %d]", cmdString, uint8(cmd))
}
// Commands used in kaspa message headers which describe the type of message.
const (
// protocol
CmdVersion MessageCommand = iota
CmdVerAck
CmdRequestAddresses
CmdAddresses
CmdRequestIBDBlocks
CmdBlock
CmdTx
CmdPing
CmdPong
CmdRequestBlockLocator
CmdBlockLocator
CmdSelectedTip
CmdRequestSelectedTip
CmdInvRelayBlock
CmdRequestRelayBlocks
CmdInvTransaction
CmdRequestTransactions
CmdIBDBlock
CmdRequestNextIBDBlocks
CmdDoneIBDBlocks
CmdTransactionNotFound
CmdReject
// rpc
CmdGetCurrentNetworkRequestMessage
CmdGetCurrentNetworkResponseMessage
CmdSubmitBlockRequestMessage
CmdSubmitBlockResponseMessage
CmdGetBlockTemplateRequestMessage
CmdGetBlockTemplateResponseMessage
CmdGetBlockTemplateTransactionMessage
CmdNotifyBlockAddedRequestMessage
CmdNotifyBlockAddedResponseMessage
CmdBlockAddedNotificationMessage
CmdGetPeerAddressesRequestMessage
CmdGetPeerAddressesResponseMessage
CmdGetSelectedTipHashRequestMessage
CmdGetSelectedTipHashResponseMessage
CmdGetMempoolEntryRequestMessage
CmdGetMempoolEntryResponseMessage
CmdGetConnectedPeerInfoRequestMessage
CmdGetConnectedPeerInfoResponseMessage
CmdAddPeerRequestMessage
CmdAddPeerResponseMessage
CmdSubmitTransactionRequestMessage
CmdSubmitTransactionResponseMessage
CmdNotifyChainChangedRequestMessage
CmdNotifyChainChangedResponseMessage
CmdChainChangedNotificationMessage
CmdGetBlockRequestMessage
CmdGetBlockResponseMessage
CmdGetSubnetworkRequestMessage
CmdGetSubnetworkResponseMessage
CmdGetChainFromBlockRequestMessage
CmdGetChainFromBlockResponseMessage
CmdGetBlocksRequestMessage
CmdGetBlocksResponseMessage
CmdGetBlockCountRequestMessage
CmdGetBlockCountResponseMessage
CmdGetBlockDAGInfoRequestMessage
CmdGetBlockDAGInfoResponseMessage
CmdResolveFinalityConflictRequestMessage
CmdResolveFinalityConflictResponseMessage
CmdNotifyFinalityConflictsRequestMessage
CmdNotifyFinalityConflictsResponseMessage
CmdFinalityConflictNotificationMessage
CmdFinalityConflictResolvedNotificationMessage
CmdGetMempoolEntriesRequestMessage
CmdGetMempoolEntriesResponseMessage
CmdShutDownRequestMessage
CmdShutDownResponseMessage
CmdGetHeadersRequestMessage
CmdGetHeadersResponseMessage
)
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
var ProtocolMessageCommandToString = map[MessageCommand]string{
CmdVersion: "Version",
CmdVerAck: "VerAck",
CmdRequestAddresses: "RequestAddresses",
CmdAddresses: "Addresses",
CmdRequestIBDBlocks: "RequestBlocks",
CmdBlock: "Block",
CmdTx: "Tx",
CmdPing: "Ping",
CmdPong: "Pong",
CmdRequestBlockLocator: "RequestBlockLocator",
CmdBlockLocator: "BlockLocator",
CmdSelectedTip: "SelectedTip",
CmdRequestSelectedTip: "RequestSelectedTip",
CmdInvRelayBlock: "InvRelayBlock",
CmdRequestRelayBlocks: "RequestRelayBlocks",
CmdInvTransaction: "InvTransaction",
CmdRequestTransactions: "RequestTransactions",
CmdIBDBlock: "IBDBlock",
CmdRequestNextIBDBlocks: "RequestNextIBDBlocks",
CmdDoneIBDBlocks: "DoneIBDBlocks",
CmdTransactionNotFound: "TransactionNotFound",
CmdReject: "Reject",
}
// RPCMessageCommandToString maps all MessageCommands to their string representation
var RPCMessageCommandToString = map[MessageCommand]string{
CmdGetCurrentNetworkRequestMessage: "GetCurrentNetworkRequest",
CmdGetCurrentNetworkResponseMessage: "GetCurrentNetworkResponse",
CmdSubmitBlockRequestMessage: "SubmitBlockRequest",
CmdSubmitBlockResponseMessage: "SubmitBlockResponse",
CmdGetBlockTemplateRequestMessage: "GetBlockTemplateRequest",
CmdGetBlockTemplateResponseMessage: "GetBlockTemplateResponse",
CmdGetBlockTemplateTransactionMessage: "CmdGetBlockTemplateTransaction",
CmdNotifyBlockAddedRequestMessage: "NotifyBlockAddedRequest",
CmdNotifyBlockAddedResponseMessage: "NotifyBlockAddedResponse",
CmdBlockAddedNotificationMessage: "BlockAddedNotification",
CmdGetPeerAddressesRequestMessage: "GetPeerAddressesRequest",
CmdGetPeerAddressesResponseMessage: "GetPeerAddressesResponse",
CmdGetSelectedTipHashRequestMessage: "GetSelectedTipHashRequest",
CmdGetSelectedTipHashResponseMessage: "GetSelectedTipHashResponse",
CmdGetMempoolEntryRequestMessage: "GetMempoolEntryRequest",
CmdGetMempoolEntryResponseMessage: "GetMempoolEntryResponse",
CmdGetConnectedPeerInfoRequestMessage: "GetConnectedPeerInfoRequest",
CmdGetConnectedPeerInfoResponseMessage: "GetConnectedPeerInfoResponse",
CmdAddPeerRequestMessage: "AddPeerRequest",
CmdAddPeerResponseMessage: "AddPeerResponse",
CmdSubmitTransactionRequestMessage: "SubmitTransactionRequest",
CmdSubmitTransactionResponseMessage: "SubmitTransactionResponse",
CmdNotifyChainChangedRequestMessage: "NotifyChainChangedRequest",
CmdNotifyChainChangedResponseMessage: "NotifyChainChangedResponse",
CmdChainChangedNotificationMessage: "ChainChangedNotification",
CmdGetBlockRequestMessage: "GetBlockRequest",
CmdGetBlockResponseMessage: "GetBlockResponse",
CmdGetSubnetworkRequestMessage: "GetSubnetworkRequest",
CmdGetSubnetworkResponseMessage: "GetSubnetworkResponse",
CmdGetChainFromBlockRequestMessage: "GetChainFromBlockRequest",
CmdGetChainFromBlockResponseMessage: "GetChainFromBlockResponse",
CmdGetBlocksRequestMessage: "GetBlocksRequest",
CmdGetBlocksResponseMessage: "GetBlocksResponse",
CmdGetBlockCountRequestMessage: "GetBlockCountRequest",
CmdGetBlockCountResponseMessage: "GetBlockCountResponse",
CmdGetBlockDAGInfoRequestMessage: "GetBlockDAGInfoRequest",
CmdGetBlockDAGInfoResponseMessage: "GetBlockDAGInfoResponse",
CmdResolveFinalityConflictRequestMessage: "ResolveFinalityConflictRequest",
CmdResolveFinalityConflictResponseMessage: "ResolveFinalityConflictResponse",
CmdNotifyFinalityConflictsRequestMessage: "NotifyFinalityConflictsRequest",
CmdNotifyFinalityConflictsResponseMessage: "NotifyFinalityConflictsResponse",
CmdFinalityConflictNotificationMessage: "FinalityConflictNotification",
CmdFinalityConflictResolvedNotificationMessage: "FinalityConflictResolvedNotification",
CmdGetMempoolEntriesRequestMessage: "GetMempoolEntriesRequestMessage",
CmdGetMempoolEntriesResponseMessage: "GetMempoolEntriesResponseMessage",
CmdGetHeadersRequestMessage: "GetHeadersRequest",
CmdGetHeadersResponseMessage: "GetHeadersResponse",
}
// Message is an interface that describes a kaspa message. A type that
// implements Message has complete control over the representation of its data
// and may therefore contain additional or fewer fields than those which
// are used directly in the protocol encoded message.
type Message interface {
Command() MessageCommand
MessageNumber() uint64
SetMessageNumber(index uint64)
ReceivedAt() time.Time
SetReceivedAt(receivedAt time.Time)
}

View File

@@ -1,22 +0,0 @@
package appmessage
// MsgReject implements the Message interface and represents a kaspa
// Reject message. It is used to notify peers why they are banned.
type MsgReject struct {
baseMessage
Reason string
}
// Command returns the protocol command string for the message. This is part
// of the Message interface implementation.
func (msg *MsgReject) Command() MessageCommand {
return CmdReject
}
// NewMsgReject returns a new kaspa Reject message that conforms to the
// Message interface.
func NewMsgReject(reason string) *MsgReject {
return &MsgReject{
Reason: reason,
}
}

View File

@@ -1,39 +0,0 @@
package appmessage
// AddPeerRequestMessage is an appmessage corresponding to
// its respective RPC message
type AddPeerRequestMessage struct {
baseMessage
Address string
IsPermanent bool
}
// Command returns the protocol command string for the message
func (msg *AddPeerRequestMessage) Command() MessageCommand {
return CmdAddPeerRequestMessage
}
// NewAddPeerRequestMessage returns a instance of the message
func NewAddPeerRequestMessage(address string, isPermanent bool) *AddPeerRequestMessage {
return &AddPeerRequestMessage{
Address: address,
IsPermanent: isPermanent,
}
}
// AddPeerResponseMessage is an appmessage corresponding to
// its respective RPC message
type AddPeerResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *AddPeerResponseMessage) Command() MessageCommand {
return CmdAddPeerResponseMessage
}
// NewAddPeerResponseMessage returns a instance of the message
func NewAddPeerResponseMessage() *AddPeerResponseMessage {
return &AddPeerResponseMessage{}
}

View File

@@ -1,123 +0,0 @@
package appmessage
// GetBlockRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetBlockRequestMessage struct {
baseMessage
Hash string
SubnetworkID string
IncludeBlockHex bool
IncludeBlockVerboseData bool
IncludeTransactionVerboseData bool
}
// Command returns the protocol command string for the message
func (msg *GetBlockRequestMessage) Command() MessageCommand {
return CmdGetBlockRequestMessage
}
// NewGetBlockRequestMessage returns a instance of the message
func NewGetBlockRequestMessage(hash string, subnetworkID string, includeBlockHex bool,
includeBlockVerboseData bool, includeTransactionVerboseData bool) *GetBlockRequestMessage {
return &GetBlockRequestMessage{
Hash: hash,
SubnetworkID: subnetworkID,
IncludeBlockHex: includeBlockHex,
IncludeBlockVerboseData: includeBlockVerboseData,
IncludeTransactionVerboseData: includeTransactionVerboseData,
}
}
// GetBlockResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetBlockResponseMessage struct {
baseMessage
BlockHex string
BlockVerboseData *BlockVerboseData
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetBlockResponseMessage) Command() MessageCommand {
return CmdGetBlockResponseMessage
}
// NewGetBlockResponseMessage returns a instance of the message
func NewGetBlockResponseMessage() *GetBlockResponseMessage {
return &GetBlockResponseMessage{}
}
// BlockVerboseData holds verbose data about a block
type BlockVerboseData struct {
Hash string
Confirmations uint64
Size int32
BlueScore uint64
IsChainBlock bool
Version int32
VersionHex string
HashMerkleRoot string
AcceptedIDMerkleRoot string
UTXOCommitment string
TxIDs []string
TransactionVerboseData []*TransactionVerboseData
Time int64
Nonce uint64
Bits string
Difficulty float64
ParentHashes []string
SelectedParentHash string
ChildHashes []string
AcceptedBlockHashes []string
}
// TransactionVerboseData holds verbose data about a transaction
type TransactionVerboseData struct {
Hex string
TxID string
Hash string
Size int32
Version int32
LockTime uint64
SubnetworkID string
Gas uint64
PayloadHash string
Payload string
TransactionVerboseInputs []*TransactionVerboseInput
TransactionVerboseOutputs []*TransactionVerboseOutput
BlockHash string
AcceptedBy string
IsInMempool bool
Time uint64
BlockTime uint64
}
// TransactionVerboseInput holds data about a transaction input
type TransactionVerboseInput struct {
TxID string
OutputIndex uint32
ScriptSig *ScriptSig
Sequence uint64
}
// ScriptSig holds data about a script signature
type ScriptSig struct {
Asm string
Hex string
}
// TransactionVerboseOutput holds data about a transaction output
type TransactionVerboseOutput struct {
Value uint64
Index uint32
ScriptPubKey *ScriptPubKeyResult
}
// ScriptPubKeyResult holds data about a script public key
type ScriptPubKeyResult struct {
Asm string
Hex string
Type string
Address string
}

View File

@@ -1,38 +0,0 @@
package appmessage
// GetBlockCountRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetBlockCountRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *GetBlockCountRequestMessage) Command() MessageCommand {
return CmdGetBlockCountRequestMessage
}
// NewGetBlockCountRequestMessage returns a instance of the message
func NewGetBlockCountRequestMessage() *GetBlockCountRequestMessage {
return &GetBlockCountRequestMessage{}
}
// GetBlockCountResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetBlockCountResponseMessage struct {
baseMessage
BlockCount uint64
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetBlockCountResponseMessage) Command() MessageCommand {
return CmdGetBlockCountResponseMessage
}
// NewGetBlockCountResponseMessage returns a instance of the message
func NewGetBlockCountResponseMessage(blockCount uint64) *GetBlockCountResponseMessage {
return &GetBlockCountResponseMessage{
BlockCount: blockCount,
}
}

View File

@@ -1,41 +0,0 @@
package appmessage
// GetBlockDAGInfoRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetBlockDAGInfoRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *GetBlockDAGInfoRequestMessage) Command() MessageCommand {
return CmdGetBlockDAGInfoRequestMessage
}
// NewGetBlockDAGInfoRequestMessage returns a instance of the message
func NewGetBlockDAGInfoRequestMessage() *GetBlockDAGInfoRequestMessage {
return &GetBlockDAGInfoRequestMessage{}
}
// GetBlockDAGInfoResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetBlockDAGInfoResponseMessage struct {
baseMessage
NetworkName string
BlockCount uint64
TipHashes []string
VirtualParentHashes []string
Difficulty float64
PastMedianTime int64
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetBlockDAGInfoResponseMessage) Command() MessageCommand {
return CmdGetBlockDAGInfoResponseMessage
}
// NewGetBlockDAGInfoResponseMessage returns a instance of the message
func NewGetBlockDAGInfoResponseMessage() *GetBlockDAGInfoResponseMessage {
return &GetBlockDAGInfoResponseMessage{}
}

View File

@@ -1,78 +0,0 @@
package appmessage
// GetBlockTemplateRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetBlockTemplateRequestMessage struct {
baseMessage
PayAddress string
LongPollID string
}
// Command returns the protocol command string for the message
func (msg *GetBlockTemplateRequestMessage) Command() MessageCommand {
return CmdGetBlockTemplateRequestMessage
}
// NewGetBlockTemplateRequestMessage returns a instance of the message
func NewGetBlockTemplateRequestMessage(payAddress string, longPollID string) *GetBlockTemplateRequestMessage {
return &GetBlockTemplateRequestMessage{
PayAddress: payAddress,
LongPollID: longPollID,
}
}
// GetBlockTemplateResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetBlockTemplateResponseMessage struct {
baseMessage
Bits string
CurrentTime int64
ParentHashes []string
MassLimit int
Transactions []GetBlockTemplateTransactionMessage
HashMerkleRoot string
AcceptedIDMerkleRoot string
UTXOCommitment string
Version int32
LongPollID string
TargetDifficulty string
MinTime int64
MaxTime int64
MutableFields []string
NonceRange string
IsSynced bool
IsConnected bool
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetBlockTemplateResponseMessage) Command() MessageCommand {
return CmdGetBlockTemplateResponseMessage
}
// NewGetBlockTemplateResponseMessage returns a instance of the message
func NewGetBlockTemplateResponseMessage() *GetBlockTemplateResponseMessage {
return &GetBlockTemplateResponseMessage{}
}
// GetBlockTemplateTransactionMessage is an appmessage corresponding to
// its respective RPC message
type GetBlockTemplateTransactionMessage struct {
baseMessage
Data string
ID string
Depends []int64
Mass uint64
Fee uint64
}
// Command returns the protocol command string for the message
func (msg *GetBlockTemplateTransactionMessage) Command() MessageCommand {
return CmdGetBlockTemplateTransactionMessage
}
// NewGetBlockTemplateTransactionMessage returns a instance of the message
func NewGetBlockTemplateTransactionMessage() *GetBlockTemplateTransactionMessage {
return &GetBlockTemplateTransactionMessage{}
}

View File

@@ -1,51 +0,0 @@
package appmessage
// GetBlocksRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetBlocksRequestMessage struct {
baseMessage
LowHash string
IncludeBlockHexes bool
IncludeBlockVerboseData bool
}
// Command returns the protocol command string for the message
func (msg *GetBlocksRequestMessage) Command() MessageCommand {
return CmdGetBlocksRequestMessage
}
// NewGetBlocksRequestMessage returns a instance of the message
func NewGetBlocksRequestMessage(lowHash string, includeBlockHexes bool, includeBlockVerboseData bool) *GetBlocksRequestMessage {
return &GetBlocksRequestMessage{
LowHash: lowHash,
IncludeBlockHexes: includeBlockHexes,
IncludeBlockVerboseData: includeBlockVerboseData,
}
}
// GetBlocksResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetBlocksResponseMessage struct {
baseMessage
BlockHashes []string
BlockHexes []string
BlockVerboseData []*BlockVerboseData
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetBlocksResponseMessage) Command() MessageCommand {
return CmdGetBlocksResponseMessage
}
// NewGetBlocksResponseMessage returns a instance of the message
func NewGetBlocksResponseMessage(blockHashes []string, blockHexes []string,
blockVerboseData []*BlockVerboseData) *GetBlocksResponseMessage {
return &GetBlocksResponseMessage{
BlockHashes: blockHashes,
BlockHexes: blockHexes,
BlockVerboseData: blockVerboseData,
}
}

View File

@@ -1,49 +0,0 @@
package appmessage
// GetChainFromBlockRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetChainFromBlockRequestMessage struct {
baseMessage
StartHash string
IncludeBlockVerboseData bool
}
// Command returns the protocol command string for the message
func (msg *GetChainFromBlockRequestMessage) Command() MessageCommand {
return CmdGetChainFromBlockRequestMessage
}
// NewGetChainFromBlockRequestMessage returns a instance of the message
func NewGetChainFromBlockRequestMessage(startHash string, includeBlockVerboseData bool) *GetChainFromBlockRequestMessage {
return &GetChainFromBlockRequestMessage{
StartHash: startHash,
IncludeBlockVerboseData: includeBlockVerboseData,
}
}
// GetChainFromBlockResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetChainFromBlockResponseMessage struct {
baseMessage
RemovedChainBlockHashes []string
AddedChainBlocks []*ChainBlock
BlockVerboseData []*BlockVerboseData
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetChainFromBlockResponseMessage) Command() MessageCommand {
return CmdGetChainFromBlockResponseMessage
}
// NewGetChainFromBlockResponseMessage returns a instance of the message
func NewGetChainFromBlockResponseMessage(removedChainBlockHashes []string,
addedChainBlocks []*ChainBlock, blockVerboseData []*BlockVerboseData) *GetChainFromBlockResponseMessage {
return &GetChainFromBlockResponseMessage{
RemovedChainBlockHashes: removedChainBlockHashes,
AddedChainBlocks: addedChainBlocks,
BlockVerboseData: blockVerboseData,
}
}

View File

@@ -1,51 +0,0 @@
package appmessage
// GetConnectedPeerInfoRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetConnectedPeerInfoRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *GetConnectedPeerInfoRequestMessage) Command() MessageCommand {
return CmdGetConnectedPeerInfoRequestMessage
}
// NewGetConnectedPeerInfoRequestMessage returns a instance of the message
func NewGetConnectedPeerInfoRequestMessage() *GetConnectedPeerInfoRequestMessage {
return &GetConnectedPeerInfoRequestMessage{}
}
// GetConnectedPeerInfoResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetConnectedPeerInfoResponseMessage struct {
baseMessage
Infos []*GetConnectedPeerInfoMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetConnectedPeerInfoResponseMessage) Command() MessageCommand {
return CmdGetConnectedPeerInfoResponseMessage
}
// NewGetConnectedPeerInfoResponseMessage returns a instance of the message
func NewGetConnectedPeerInfoResponseMessage(infos []*GetConnectedPeerInfoMessage) *GetConnectedPeerInfoResponseMessage {
return &GetConnectedPeerInfoResponseMessage{
Infos: infos,
}
}
// GetConnectedPeerInfoMessage holds information about a connected peer
type GetConnectedPeerInfoMessage struct {
ID string
Address string
LastPingDuration int64
SelectedTipHash string
IsSyncNode bool
IsOutbound bool
TimeOffset int64
UserAgent string
AdvertisedProtocolVersion uint32
TimeConnected int64
}

View File

@@ -1,38 +0,0 @@
package appmessage
// GetCurrentNetworkRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetCurrentNetworkRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *GetCurrentNetworkRequestMessage) Command() MessageCommand {
return CmdGetCurrentNetworkRequestMessage
}
// NewGetCurrentNetworkRequestMessage returns a instance of the message
func NewGetCurrentNetworkRequestMessage() *GetCurrentNetworkRequestMessage {
return &GetCurrentNetworkRequestMessage{}
}
// GetCurrentNetworkResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetCurrentNetworkResponseMessage struct {
baseMessage
CurrentNetwork string
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetCurrentNetworkResponseMessage) Command() MessageCommand {
return CmdGetCurrentNetworkResponseMessage
}
// NewGetCurrentNetworkResponseMessage returns a instance of the message
func NewGetCurrentNetworkResponseMessage(currentNetwork string) *GetCurrentNetworkResponseMessage {
return &GetCurrentNetworkResponseMessage{
CurrentNetwork: currentNetwork,
}
}

View File

@@ -1,45 +0,0 @@
package appmessage
// GetHeadersRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetHeadersRequestMessage struct {
baseMessage
StartHash string
Limit uint64
IsAscending bool
}
// Command returns the protocol command string for the message
func (msg *GetHeadersRequestMessage) Command() MessageCommand {
return CmdGetHeadersRequestMessage
}
// NewGetHeadersRequestMessage returns a instance of the message
func NewGetHeadersRequestMessage(startHash string, limit uint64, isAscending bool) *GetHeadersRequestMessage {
return &GetHeadersRequestMessage{
StartHash: startHash,
Limit: limit,
IsAscending: isAscending,
}
}
// GetHeadersResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetHeadersResponseMessage struct {
baseMessage
Headers []string
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetHeadersResponseMessage) Command() MessageCommand {
return CmdGetHeadersResponseMessage
}
// NewGetHeadersResponseMessage returns a instance of the message
func NewGetHeadersResponseMessage(headers []string) *GetHeadersResponseMessage {
return &GetHeadersResponseMessage{
Headers: headers,
}
}

View File

@@ -1,38 +0,0 @@
package appmessage
// GetMempoolEntriesRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetMempoolEntriesRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *GetMempoolEntriesRequestMessage) Command() MessageCommand {
return CmdGetMempoolEntriesRequestMessage
}
// NewGetMempoolEntriesRequestMessage returns a instance of the message
func NewGetMempoolEntriesRequestMessage() *GetMempoolEntriesRequestMessage {
return &GetMempoolEntriesRequestMessage{}
}
// GetMempoolEntriesResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetMempoolEntriesResponseMessage struct {
baseMessage
Entries []*MempoolEntry
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetMempoolEntriesResponseMessage) Command() MessageCommand {
return CmdGetMempoolEntriesResponseMessage
}
// NewGetMempoolEntriesResponseMessage returns a instance of the message
func NewGetMempoolEntriesResponseMessage(entries []*MempoolEntry) *GetMempoolEntriesResponseMessage {
return &GetMempoolEntriesResponseMessage{
Entries: entries,
}
}

View File

@@ -1,48 +0,0 @@
package appmessage
// GetMempoolEntryRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetMempoolEntryRequestMessage struct {
baseMessage
TxID string
}
// Command returns the protocol command string for the message
func (msg *GetMempoolEntryRequestMessage) Command() MessageCommand {
return CmdGetMempoolEntryRequestMessage
}
// NewGetMempoolEntryRequestMessage returns a instance of the message
func NewGetMempoolEntryRequestMessage(txID string) *GetMempoolEntryRequestMessage {
return &GetMempoolEntryRequestMessage{TxID: txID}
}
// GetMempoolEntryResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetMempoolEntryResponseMessage struct {
baseMessage
Entry *MempoolEntry
Error *RPCError
}
// MempoolEntry represents a transaction in the mempool.
type MempoolEntry struct {
Fee uint64
TransactionVerboseData *TransactionVerboseData
}
// Command returns the protocol command string for the message
func (msg *GetMempoolEntryResponseMessage) Command() MessageCommand {
return CmdGetMempoolEntryResponseMessage
}
// NewGetMempoolEntryResponseMessage returns a instance of the message
func NewGetMempoolEntryResponseMessage(fee uint64, transactionVerboseData *TransactionVerboseData) *GetMempoolEntryResponseMessage {
return &GetMempoolEntryResponseMessage{
Entry: &MempoolEntry{
Fee: fee,
TransactionVerboseData: transactionVerboseData,
},
}
}

View File

@@ -1,46 +0,0 @@
package appmessage
// GetPeerAddressesRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetPeerAddressesRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *GetPeerAddressesRequestMessage) Command() MessageCommand {
return CmdGetPeerAddressesRequestMessage
}
// NewGetPeerAddressesRequestMessage returns a instance of the message
func NewGetPeerAddressesRequestMessage() *GetPeerAddressesRequestMessage {
return &GetPeerAddressesRequestMessage{}
}
// GetPeerAddressesResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetPeerAddressesResponseMessage struct {
baseMessage
Addresses []*GetPeerAddressesKnownAddressMessage
BannedAddresses []*GetPeerAddressesKnownAddressMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetPeerAddressesResponseMessage) Command() MessageCommand {
return CmdGetPeerAddressesResponseMessage
}
// NewGetPeerAddressesResponseMessage returns a instance of the message
func NewGetPeerAddressesResponseMessage(addresses []*GetPeerAddressesKnownAddressMessage, bannedAddresses []*GetPeerAddressesKnownAddressMessage) *GetPeerAddressesResponseMessage {
return &GetPeerAddressesResponseMessage{
Addresses: addresses,
BannedAddresses: bannedAddresses,
}
}
// GetPeerAddressesKnownAddressMessage is an appmessage corresponding to
// its respective RPC message
type GetPeerAddressesKnownAddressMessage struct {
Addr string
}

View File

@@ -1,38 +0,0 @@
package appmessage
// GetSelectedTipHashRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetSelectedTipHashRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *GetSelectedTipHashRequestMessage) Command() MessageCommand {
return CmdGetSelectedTipHashRequestMessage
}
// NewGetSelectedTipHashRequestMessage returns a instance of the message
func NewGetSelectedTipHashRequestMessage() *GetSelectedTipHashRequestMessage {
return &GetSelectedTipHashRequestMessage{}
}
// GetSelectedTipHashResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetSelectedTipHashResponseMessage struct {
baseMessage
SelectedTipHash string
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetSelectedTipHashResponseMessage) Command() MessageCommand {
return CmdGetSelectedTipHashResponseMessage
}
// NewGetSelectedTipHashResponseMessage returns a instance of the message
func NewGetSelectedTipHashResponseMessage(selectedTipHash string) *GetSelectedTipHashResponseMessage {
return &GetSelectedTipHashResponseMessage{
SelectedTipHash: selectedTipHash,
}
}

View File

@@ -1,41 +0,0 @@
package appmessage
// GetSubnetworkRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetSubnetworkRequestMessage struct {
baseMessage
SubnetworkID string
}
// Command returns the protocol command string for the message
func (msg *GetSubnetworkRequestMessage) Command() MessageCommand {
return CmdGetSubnetworkRequestMessage
}
// NewGetSubnetworkRequestMessage returns a instance of the message
func NewGetSubnetworkRequestMessage(subnetworkID string) *GetSubnetworkRequestMessage {
return &GetSubnetworkRequestMessage{
SubnetworkID: subnetworkID,
}
}
// GetSubnetworkResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetSubnetworkResponseMessage struct {
baseMessage
GasLimit uint64
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetSubnetworkResponseMessage) Command() MessageCommand {
return CmdGetSubnetworkResponseMessage
}
// NewGetSubnetworkResponseMessage returns a instance of the message
func NewGetSubnetworkResponseMessage(gasLimit uint64) *GetSubnetworkResponseMessage {
return &GetSubnetworkResponseMessage{
GasLimit: gasLimit,
}
}

View File

@@ -1,53 +0,0 @@
package appmessage
// NotifyBlockAddedRequestMessage is an appmessage corresponding to
// its respective RPC message
type NotifyBlockAddedRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *NotifyBlockAddedRequestMessage) Command() MessageCommand {
return CmdNotifyBlockAddedRequestMessage
}
// NewNotifyBlockAddedRequestMessage returns a instance of the message
func NewNotifyBlockAddedRequestMessage() *NotifyBlockAddedRequestMessage {
return &NotifyBlockAddedRequestMessage{}
}
// NotifyBlockAddedResponseMessage is an appmessage corresponding to
// its respective RPC message
type NotifyBlockAddedResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *NotifyBlockAddedResponseMessage) Command() MessageCommand {
return CmdNotifyBlockAddedResponseMessage
}
// NewNotifyBlockAddedResponseMessage returns a instance of the message
func NewNotifyBlockAddedResponseMessage() *NotifyBlockAddedResponseMessage {
return &NotifyBlockAddedResponseMessage{}
}
// BlockAddedNotificationMessage is an appmessage corresponding to
// its respective RPC message
type BlockAddedNotificationMessage struct {
baseMessage
Block *MsgBlock
}
// Command returns the protocol command string for the message
func (msg *BlockAddedNotificationMessage) Command() MessageCommand {
return CmdBlockAddedNotificationMessage
}
// NewBlockAddedNotificationMessage returns a instance of the message
func NewBlockAddedNotificationMessage(block *MsgBlock) *BlockAddedNotificationMessage {
return &BlockAddedNotificationMessage{
Block: block,
}
}

View File

@@ -1,69 +0,0 @@
package appmessage
// NotifyChainChangedRequestMessage is an appmessage corresponding to
// its respective RPC message
type NotifyChainChangedRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *NotifyChainChangedRequestMessage) Command() MessageCommand {
return CmdNotifyChainChangedRequestMessage
}
// NewNotifyChainChangedRequestMessage returns a instance of the message
func NewNotifyChainChangedRequestMessage() *NotifyChainChangedRequestMessage {
return &NotifyChainChangedRequestMessage{}
}
// NotifyChainChangedResponseMessage is an appmessage corresponding to
// its respective RPC message
type NotifyChainChangedResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *NotifyChainChangedResponseMessage) Command() MessageCommand {
return CmdNotifyChainChangedResponseMessage
}
// NewNotifyChainChangedResponseMessage returns a instance of the message
func NewNotifyChainChangedResponseMessage() *NotifyChainChangedResponseMessage {
return &NotifyChainChangedResponseMessage{}
}
// ChainChangedNotificationMessage is an appmessage corresponding to
// its respective RPC message
type ChainChangedNotificationMessage struct {
baseMessage
RemovedChainBlockHashes []string
AddedChainBlocks []*ChainBlock
}
// ChainBlock represents a DAG chain-block
type ChainBlock struct {
Hash string
AcceptedBlocks []*AcceptedBlock
}
// AcceptedBlock represents a block accepted into the DAG
type AcceptedBlock struct {
Hash string
AcceptedTxIDs []string
}
// Command returns the protocol command string for the message
func (msg *ChainChangedNotificationMessage) Command() MessageCommand {
return CmdChainChangedNotificationMessage
}
// NewChainChangedNotificationMessage returns a instance of the message
func NewChainChangedNotificationMessage(removedChainBlockHashes []string,
addedChainBlocks []*ChainBlock) *ChainChangedNotificationMessage {
return &ChainChangedNotificationMessage{
RemovedChainBlockHashes: removedChainBlockHashes,
AddedChainBlocks: addedChainBlocks,
}
}

View File

@@ -1,72 +0,0 @@
package appmessage
// NotifyFinalityConflictsRequestMessage is an appmessage corresponding to
// its respective RPC message
type NotifyFinalityConflictsRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *NotifyFinalityConflictsRequestMessage) Command() MessageCommand {
return CmdNotifyFinalityConflictsRequestMessage
}
// NewNotifyFinalityConflictsRequestMessage returns a instance of the message
func NewNotifyFinalityConflictsRequestMessage() *NotifyFinalityConflictsRequestMessage {
return &NotifyFinalityConflictsRequestMessage{}
}
// NotifyFinalityConflictsResponseMessage is an appmessage corresponding to
// its respective RPC message
type NotifyFinalityConflictsResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *NotifyFinalityConflictsResponseMessage) Command() MessageCommand {
return CmdNotifyFinalityConflictsResponseMessage
}
// NewNotifyFinalityConflictsResponseMessage returns a instance of the message
func NewNotifyFinalityConflictsResponseMessage() *NotifyFinalityConflictsResponseMessage {
return &NotifyFinalityConflictsResponseMessage{}
}
// FinalityConflictNotificationMessage is an appmessage corresponding to
// its respective RPC message
type FinalityConflictNotificationMessage struct {
baseMessage
ViolatingBlockHash string
}
// Command returns the protocol command string for the message
func (msg *FinalityConflictNotificationMessage) Command() MessageCommand {
return CmdFinalityConflictNotificationMessage
}
// NewFinalityConflictNotificationMessage returns a instance of the message
func NewFinalityConflictNotificationMessage(violatingBlockHash string) *FinalityConflictNotificationMessage {
return &FinalityConflictNotificationMessage{
ViolatingBlockHash: violatingBlockHash,
}
}
// FinalityConflictResolvedNotificationMessage is an appmessage corresponding to
// its respective RPC message
type FinalityConflictResolvedNotificationMessage struct {
baseMessage
FinalityBlockHash string
}
// Command returns the protocol command string for the message
func (msg *FinalityConflictResolvedNotificationMessage) Command() MessageCommand {
return CmdFinalityConflictResolvedNotificationMessage
}
// NewFinalityConflictResolvedNotificationMessage returns a instance of the message
func NewFinalityConflictResolvedNotificationMessage(finalityBlockHash string) *FinalityConflictResolvedNotificationMessage {
return &FinalityConflictResolvedNotificationMessage{
FinalityBlockHash: finalityBlockHash,
}
}

View File

@@ -1,37 +0,0 @@
package appmessage
// ResolveFinalityConflictRequestMessage is an appmessage corresponding to
// its respective RPC message
type ResolveFinalityConflictRequestMessage struct {
baseMessage
FinalityBlockHash string
}
// Command returns the protocol command string for the message
func (msg *ResolveFinalityConflictRequestMessage) Command() MessageCommand {
return CmdResolveFinalityConflictRequestMessage
}
// NewResolveFinalityConflictRequestMessage returns a instance of the message
func NewResolveFinalityConflictRequestMessage(finalityBlockHash string) *ResolveFinalityConflictRequestMessage {
return &ResolveFinalityConflictRequestMessage{
FinalityBlockHash: finalityBlockHash,
}
}
// ResolveFinalityConflictResponseMessage is an appmessage corresponding to
// its respective RPC message
type ResolveFinalityConflictResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *ResolveFinalityConflictResponseMessage) Command() MessageCommand {
return CmdResolveFinalityConflictResponseMessage
}
// NewResolveFinalityConflictResponseMessage returns a instance of the message
func NewResolveFinalityConflictResponseMessage() *ResolveFinalityConflictResponseMessage {
return &ResolveFinalityConflictResponseMessage{}
}

View File

@@ -1,34 +0,0 @@
package appmessage
// ShutDownRequestMessage is an appmessage corresponding to
// its respective RPC message
type ShutDownRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *ShutDownRequestMessage) Command() MessageCommand {
return CmdShutDownRequestMessage
}
// NewShutDownRequestMessage returns a instance of the message
func NewShutDownRequestMessage() *ShutDownRequestMessage {
return &ShutDownRequestMessage{}
}
// ShutDownResponseMessage is an appmessage corresponding to
// its respective RPC message
type ShutDownResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *ShutDownResponseMessage) Command() MessageCommand {
return CmdShutDownResponseMessage
}
// NewShutDownResponseMessage returns a instance of the message
func NewShutDownResponseMessage() *ShutDownResponseMessage {
return &ShutDownResponseMessage{}
}

View File

@@ -1,37 +0,0 @@
package appmessage
// SubmitBlockRequestMessage is an appmessage corresponding to
// its respective RPC message
type SubmitBlockRequestMessage struct {
baseMessage
BlockHex string
}
// Command returns the protocol command string for the message
func (msg *SubmitBlockRequestMessage) Command() MessageCommand {
return CmdSubmitBlockRequestMessage
}
// NewSubmitBlockRequestMessage returns a instance of the message
func NewSubmitBlockRequestMessage(blockHex string) *SubmitBlockRequestMessage {
return &SubmitBlockRequestMessage{
BlockHex: blockHex,
}
}
// SubmitBlockResponseMessage is an appmessage corresponding to
// its respective RPC message
type SubmitBlockResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *SubmitBlockResponseMessage) Command() MessageCommand {
return CmdSubmitBlockResponseMessage
}
// NewSubmitBlockResponseMessage returns a instance of the message
func NewSubmitBlockResponseMessage() *SubmitBlockResponseMessage {
return &SubmitBlockResponseMessage{}
}

View File

@@ -1,41 +0,0 @@
package appmessage
// SubmitTransactionRequestMessage is an appmessage corresponding to
// its respective RPC message
type SubmitTransactionRequestMessage struct {
baseMessage
TransactionHex string
}
// Command returns the protocol command string for the message
func (msg *SubmitTransactionRequestMessage) Command() MessageCommand {
return CmdSubmitTransactionRequestMessage
}
// NewSubmitTransactionRequestMessage returns a instance of the message
func NewSubmitTransactionRequestMessage(transactionHex string) *SubmitTransactionRequestMessage {
return &SubmitTransactionRequestMessage{
TransactionHex: transactionHex,
}
}
// SubmitTransactionResponseMessage is an appmessage corresponding to
// its respective RPC message
type SubmitTransactionResponseMessage struct {
baseMessage
TxID string
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *SubmitTransactionResponseMessage) Command() MessageCommand {
return CmdSubmitTransactionResponseMessage
}
// NewSubmitTransactionResponseMessage returns a instance of the message
func NewSubmitTransactionResponseMessage(txID string) *SubmitTransactionResponseMessage {
return &SubmitTransactionResponseMessage{
TxID: txID,
}
}

View File

@@ -1,259 +0,0 @@
package app
import (
"fmt"
"sync/atomic"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/protocol"
"github.com/kaspanet/kaspad/app/rpc"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
"github.com/kaspanet/kaspad/domain/mempool"
"github.com/kaspanet/kaspad/domain/mining"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
"github.com/kaspanet/kaspad/infrastructure/network/dnsseed"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
"github.com/kaspanet/kaspad/util/panics"
)
// ComponentManager is a wrapper for all the kaspad services
type ComponentManager struct {
cfg *config.Config
addressManager *addressmanager.AddressManager
protocolManager *protocol.Manager
rpcManager *rpc.Manager
connectionManager *connmanager.ConnectionManager
netAdapter *netadapter.NetAdapter
started, shutdown int32
}
// Start launches all the kaspad services.
func (a *ComponentManager) Start() {
// Already started?
if atomic.AddInt32(&a.started, 1) != 1 {
return
}
log.Trace("Starting kaspad")
err := a.netAdapter.Start()
if err != nil {
panics.Exit(log, fmt.Sprintf("Error starting the net adapter: %+v", err))
}
a.maybeSeedFromDNS()
a.connectionManager.Start()
}
// Stop gracefully shuts down all the kaspad services.
func (a *ComponentManager) Stop() {
// Make sure this only happens once.
if atomic.AddInt32(&a.shutdown, 1) != 1 {
log.Infof("Kaspad is already in the process of shutting down")
return
}
log.Warnf("Kaspad shutting down")
a.connectionManager.Stop()
err := a.netAdapter.Stop()
if err != nil {
log.Errorf("Error stopping the net adapter: %+v", err)
}
return
}
// NewComponentManager returns a new ComponentManager instance.
// Use Start() to begin all services within this ComponentManager
func NewComponentManager(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt chan<- struct{}) (*ComponentManager, error) {
indexManager, acceptanceIndex := setupIndexes(cfg)
sigCache := txscript.NewSigCache(cfg.SigCacheMaxSize)
// Create a new block DAG instance with the appropriate configuration.
dag, err := setupDAG(cfg, databaseContext, sigCache, indexManager)
if err != nil {
return nil, err
}
txMempool := setupMempool(cfg, dag, sigCache)
netAdapter, err := netadapter.NewNetAdapter(cfg)
if err != nil {
return nil, err
}
addressManager, err := addressmanager.New(addressmanager.NewConfig(cfg))
if err != nil {
return nil, err
}
connectionManager, err := connmanager.New(cfg, netAdapter, addressManager)
if err != nil {
return nil, err
}
protocolManager, err := protocol.NewManager(cfg, dag, netAdapter, addressManager, txMempool, connectionManager)
if err != nil {
return nil, err
}
rpcManager := setupRPC(cfg, txMempool, dag, sigCache, netAdapter, protocolManager, connectionManager, addressManager, acceptanceIndex, interrupt)
return &ComponentManager{
cfg: cfg,
protocolManager: protocolManager,
rpcManager: rpcManager,
connectionManager: connectionManager,
netAdapter: netAdapter,
addressManager: addressManager,
}, nil
}
func setupRPC(
cfg *config.Config,
txMempool *mempool.TxPool,
dag *blockdag.BlockDAG,
sigCache *txscript.SigCache,
netAdapter *netadapter.NetAdapter,
protocolManager *protocol.Manager,
connectionManager *connmanager.ConnectionManager,
addressManager *addressmanager.AddressManager,
acceptanceIndex *indexers.AcceptanceIndex,
shutDownChan chan<- struct{},
) *rpc.Manager {
blockTemplateGenerator := mining.NewBlkTmplGenerator(&mining.Policy{BlockMaxMass: cfg.BlockMaxMass}, txMempool, dag, sigCache)
rpcManager := rpc.NewManager(cfg, netAdapter, dag, protocolManager, connectionManager, blockTemplateGenerator, txMempool, addressManager, acceptanceIndex, shutDownChan)
protocolManager.SetOnBlockAddedToDAGHandler(rpcManager.NotifyBlockAddedToDAG)
protocolManager.SetOnTransactionAddedToMempoolHandler(rpcManager.NotifyTransactionAddedToMempool)
dag.Subscribe(func(notification *blockdag.Notification) {
err := handleBlockDAGNotifications(notification, acceptanceIndex, rpcManager)
if err != nil {
panic(err)
}
})
return rpcManager
}
func handleBlockDAGNotifications(notification *blockdag.Notification,
acceptanceIndex *indexers.AcceptanceIndex, rpcManager *rpc.Manager) error {
switch notification.Type {
case blockdag.NTChainChanged:
if acceptanceIndex == nil {
return nil
}
chainChangedNotificationData := notification.Data.(*blockdag.ChainChangedNotificationData)
err := rpcManager.NotifyChainChanged(chainChangedNotificationData.RemovedChainBlockHashes,
chainChangedNotificationData.AddedChainBlockHashes)
if err != nil {
return err
}
case blockdag.NTFinalityConflict:
finalityConflictNotificationData := notification.Data.(*blockdag.FinalityConflictNotificationData)
err := rpcManager.NotifyFinalityConflict(finalityConflictNotificationData.ViolatingBlockHash.String())
if err != nil {
return err
}
case blockdag.NTFinalityConflictResolved:
finalityConflictResolvedNotificationData := notification.Data.(*blockdag.FinalityConflictResolvedNotificationData)
err := rpcManager.NotifyFinalityConflictResolved(finalityConflictResolvedNotificationData.FinalityBlockHash.String())
if err != nil {
return err
}
}
return nil
}
func (a *ComponentManager) maybeSeedFromDNS() {
if !a.cfg.DisableDNSSeed {
dnsseed.SeedFromDNS(a.cfg.NetParams(), a.cfg.DNSSeed, appmessage.SFNodeNetwork, false, nil,
a.cfg.Lookup, func(addresses []*appmessage.NetAddress) {
// Kaspad uses a lookup of the dns seeder here. Since seeder returns
// IPs of nodes and not its own IP, we can not know real IP of
// source. So we'll take first returned address as source.
a.addressManager.AddAddresses(addresses...)
})
}
if a.cfg.GRPCSeed != "" {
dnsseed.SeedFromGRPC(a.cfg.NetParams(), a.cfg.GRPCSeed, appmessage.SFNodeNetwork, false, nil,
func(addresses []*appmessage.NetAddress) {
a.addressManager.AddAddresses(addresses...)
})
}
}
func setupDAG(cfg *config.Config, databaseContext *dbaccess.DatabaseContext,
sigCache *txscript.SigCache, indexManager blockdag.IndexManager) (*blockdag.BlockDAG, error) {
dag, err := blockdag.New(&blockdag.Config{
DatabaseContext: databaseContext,
DAGParams: cfg.NetParams(),
TimeSource: blockdag.NewTimeSource(),
SigCache: sigCache,
IndexManager: indexManager,
SubnetworkID: cfg.SubnetworkID,
MaxUTXOCacheSize: cfg.MaxUTXOCacheSize,
})
return dag, err
}
func setupIndexes(cfg *config.Config) (blockdag.IndexManager, *indexers.AcceptanceIndex) {
// Create indexes if needed.
var indexes []indexers.Indexer
var acceptanceIndex *indexers.AcceptanceIndex
if cfg.AcceptanceIndex {
log.Info("acceptance index is enabled")
acceptanceIndex = indexers.NewAcceptanceIndex()
indexes = append(indexes, acceptanceIndex)
}
// Create an index manager if any of the optional indexes are enabled.
if len(indexes) < 0 {
return nil, nil
}
indexManager := indexers.NewManager(indexes)
return indexManager, acceptanceIndex
}
func setupMempool(cfg *config.Config, dag *blockdag.BlockDAG, sigCache *txscript.SigCache) *mempool.TxPool {
mempoolConfig := mempool.Config{
Policy: mempool.Policy{
AcceptNonStd: cfg.RelayNonStd,
MaxOrphanTxs: cfg.MaxOrphanTxs,
MaxOrphanTxSize: config.DefaultMaxOrphanTxSize,
MinRelayTxFee: cfg.MinRelayTxFee,
MaxTxVersion: 1,
},
CalcTxSequenceLockFromReferencedUTXOEntries: dag.CalcTxSequenceLockFromReferencedUTXOEntries,
SigCache: sigCache,
DAG: dag,
}
return mempool.New(&mempoolConfig)
}
// P2PNodeID returns the network ID associated with this ComponentManager
func (a *ComponentManager) P2PNodeID() *id.ID {
return a.netAdapter.ID()
}
// AddressManager returns the AddressManager associated with this ComponentManager
func (a *ComponentManager) AddressManager() *addressmanager.AddressManager {
return a.addressManager
}

View File

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

View File

@@ -1,31 +0,0 @@
package flowcontext
import (
"errors"
"sync/atomic"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
)
// HandleError handles an error from a flow,
// It sends the error to errChan if isStopping == 0 and increments isStopping
//
// If this is ErrRouteClosed - forward it to errChan
// If this is ProtocolError - logs the error, and forward it to errChan
// Otherwise - panics
func (*FlowContext) HandleError(err error, flowName string, isStopping *uint32, errChan chan<- error) {
isErrRouteClosed := errors.Is(err, router.ErrRouteClosed)
if !isErrRouteClosed {
if protocolErr := &(protocolerrors.ProtocolError{}); !errors.As(err, &protocolErr) {
panic(err)
}
log.Errorf("error from %s: %+v", flowName, err)
}
if atomic.AddUint32(isStopping, 1) == 1 {
errChan <- err
}
}

View File

@@ -1,42 +0,0 @@
package rejects
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleRejectsContext is the interface for the context needed for the HandleRejects flow.
type HandleRejectsContext interface {
}
type handleRejectsFlow struct {
HandleRejectsContext
incomingRoute, outgoingRoute *router.Route
}
// HandleRejects handles all reject messages coming through incomingRoute.
// This function assumes that incomingRoute will only return MsgReject.
func HandleRejects(context HandleRejectsContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
flow := &handleRejectsFlow{
HandleRejectsContext: context,
incomingRoute: incomingRoute,
outgoingRoute: outgoingRoute,
}
return flow.start()
}
func (flow *handleRejectsFlow) start() error {
message, err := flow.incomingRoute.Dequeue()
if err != nil {
return err
}
rejectMessage := message.(*appmessage.MsgReject)
const maxReasonLength = 255
if len(rejectMessage.Reason) > maxReasonLength {
return protocolerrors.Errorf(false, "got reject message longer than %d", maxReasonLength)
}
return protocolerrors.Errorf(false, "got reject message: `%s`", rejectMessage.Reason)
}

View File

@@ -1,9 +0,0 @@
package rpc
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/panics"
)
var log, _ = logger.Get(logger.SubsystemTags.RPCS)
var spawn = panics.GoroutineWrapperFunc(log)

View File

@@ -1,93 +0,0 @@
package rpc
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/protocol"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
"github.com/kaspanet/kaspad/domain/mempool"
"github.com/kaspanet/kaspad/domain/mining"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
)
// Manager is an RPC manager
type Manager struct {
context *rpccontext.Context
}
// NewManager creates a new RPC Manager
func NewManager(
cfg *config.Config,
netAdapter *netadapter.NetAdapter,
dag *blockdag.BlockDAG,
protocolManager *protocol.Manager,
connectionManager *connmanager.ConnectionManager,
blockTemplateGenerator *mining.BlkTmplGenerator,
mempool *mempool.TxPool,
addressManager *addressmanager.AddressManager,
acceptanceIndex *indexers.AcceptanceIndex,
shutDownChan chan<- struct{}) *Manager {
manager := Manager{
context: rpccontext.NewContext(
cfg,
netAdapter,
dag,
protocolManager,
connectionManager,
blockTemplateGenerator,
mempool,
addressManager,
acceptanceIndex,
shutDownChan,
),
}
netAdapter.SetRPCRouterInitializer(manager.routerInitializer)
return &manager
}
// NotifyBlockAddedToDAG notifies the manager that a block has been added to the DAG
func (m *Manager) NotifyBlockAddedToDAG(block *util.Block) error {
m.context.BlockTemplateState.NotifyBlockAdded(block)
notification := appmessage.NewBlockAddedNotificationMessage(block.MsgBlock())
return m.context.NotificationManager.NotifyBlockAdded(notification)
}
// NotifyChainChanged notifies the manager that the DAG's selected parent chain has changed
func (m *Manager) NotifyChainChanged(removedChainBlockHashes []*daghash.Hash, addedChainBlockHashes []*daghash.Hash) error {
addedChainBlocks, err := m.context.CollectChainBlocks(addedChainBlockHashes)
if err != nil {
return err
}
removedChainBlockHashStrings := make([]string, len(removedChainBlockHashes))
for i, removedChainBlockHash := range removedChainBlockHashes {
removedChainBlockHashStrings[i] = removedChainBlockHash.String()
}
notification := appmessage.NewChainChangedNotificationMessage(removedChainBlockHashStrings, addedChainBlocks)
return m.context.NotificationManager.NotifyChainChanged(notification)
}
// NotifyFinalityConflict notifies the manager that there's a finality conflict in the DAG
func (m *Manager) NotifyFinalityConflict(violatingBlockHash string) error {
notification := appmessage.NewFinalityConflictNotificationMessage(violatingBlockHash)
return m.context.NotificationManager.NotifyFinalityConflict(notification)
}
// NotifyFinalityConflictResolved notifies the manager that a finality conflict in the DAG has been resolved
func (m *Manager) NotifyFinalityConflictResolved(finalityBlockHash string) error {
notification := appmessage.NewFinalityConflictResolvedNotificationMessage(finalityBlockHash)
return m.context.NotificationManager.NotifyFinalityConflictResolved(notification)
}
// NotifyTransactionAddedToMempool notifies the manager that a transaction has been added to the mempool
func (m *Manager) NotifyTransactionAddedToMempool() {
m.context.BlockTemplateState.NotifyMempoolTx()
}

View File

@@ -1,90 +0,0 @@
package rpc
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/app/rpc/rpchandlers"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/pkg/errors"
)
type handler func(context *rpccontext.Context, router *router.Router, request appmessage.Message) (appmessage.Message, error)
var handlers = map[appmessage.MessageCommand]handler{
appmessage.CmdGetCurrentNetworkRequestMessage: rpchandlers.HandleGetCurrentNetwork,
appmessage.CmdSubmitBlockRequestMessage: rpchandlers.HandleSubmitBlock,
appmessage.CmdGetBlockTemplateRequestMessage: rpchandlers.HandleGetBlockTemplate,
appmessage.CmdNotifyBlockAddedRequestMessage: rpchandlers.HandleNotifyBlockAdded,
appmessage.CmdGetPeerAddressesRequestMessage: rpchandlers.HandleGetPeerAddresses,
appmessage.CmdGetSelectedTipHashRequestMessage: rpchandlers.HandleGetSelectedTipHash,
appmessage.CmdGetMempoolEntryRequestMessage: rpchandlers.HandleGetMempoolEntry,
appmessage.CmdGetConnectedPeerInfoRequestMessage: rpchandlers.HandleGetConnectedPeerInfo,
appmessage.CmdAddPeerRequestMessage: rpchandlers.HandleAddPeer,
appmessage.CmdSubmitTransactionRequestMessage: rpchandlers.HandleSubmitTransaction,
appmessage.CmdNotifyChainChangedRequestMessage: rpchandlers.HandleNotifyChainChanged,
appmessage.CmdGetBlockRequestMessage: rpchandlers.HandleGetBlock,
appmessage.CmdGetSubnetworkRequestMessage: rpchandlers.HandleGetSubnetwork,
appmessage.CmdGetChainFromBlockRequestMessage: rpchandlers.HandleGetChainFromBlock,
appmessage.CmdGetBlocksRequestMessage: rpchandlers.HandleGetBlocks,
appmessage.CmdGetBlockCountRequestMessage: rpchandlers.HandleGetBlockCount,
appmessage.CmdGetBlockDAGInfoRequestMessage: rpchandlers.HandleGetBlockDAGInfo,
appmessage.CmdResolveFinalityConflictRequestMessage: rpchandlers.HandleResolveFinalityConflict,
appmessage.CmdNotifyFinalityConflictsRequestMessage: rpchandlers.HandleNotifyFinalityConflicts,
appmessage.CmdGetMempoolEntriesRequestMessage: rpchandlers.HandleGetMempoolEntries,
appmessage.CmdShutDownRequestMessage: rpchandlers.HandleGetMempoolEntries,
appmessage.CmdGetHeadersRequestMessage: rpchandlers.HandleGetHeaders,
}
func (m *Manager) routerInitializer(router *router.Router, netConnection *netadapter.NetConnection) {
messageTypes := make([]appmessage.MessageCommand, 0, len(handlers))
for messageType := range handlers {
messageTypes = append(messageTypes, messageType)
}
incomingRoute, err := router.AddIncomingRoute(messageTypes)
if err != nil {
panic(err)
}
m.context.NotificationManager.AddListener(router)
spawn("routerInitializer-handleIncomingMessages", func() {
defer m.context.NotificationManager.RemoveListener(router)
err := m.handleIncomingMessages(router, incomingRoute)
m.handleError(err, netConnection)
})
}
func (m *Manager) handleIncomingMessages(router *router.Router, incomingRoute *router.Route) error {
outgoingRoute := router.OutgoingRoute()
for {
request, err := incomingRoute.Dequeue()
if err != nil {
return err
}
handler, ok := handlers[request.Command()]
if !ok {
return err
}
response, err := handler(m.context, router, request)
if err != nil {
return err
}
err = outgoingRoute.Enqueue(response)
if err != nil {
return err
}
}
}
func (m *Manager) handleError(err error, netConnection *netadapter.NetConnection) {
if errors.Is(err, router.ErrTimeout) {
log.Warnf("Got timeout from %s. Disconnecting...", netConnection)
netConnection.Disconnect()
return
}
if errors.Is(err, router.ErrRouteClosed) {
return
}
panic(err)
}

View File

@@ -1,476 +0,0 @@
package rpccontext
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/mining"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/util/random"
"github.com/pkg/errors"
"strconv"
"strings"
"sync"
"time"
)
const (
// blockTemplateNonceRange is two 64-bit big-endian hexadecimal integers which
// represent the valid ranges of nonces returned by the getBlockTemplate
// RPC.
blockTemplateNonceRange = "000000000000ffffffffffff"
// blockTemplateRegenerateSeconds is the number of seconds that must pass before
// a new template is generated when the parent block hashes has not
// changed and there have been changes to the available transactions
// in the memory pool.
blockTemplateRegenerateSeconds = 60
)
var (
// blockTemplateMutableFields are the manipulations the server allows to be made
// to block templates generated by the getBlockTemplate RPC. It is
// declared here to avoid the overhead of creating the slice on every
// invocation for constant data.
blockTemplateMutableFields = []string{
"time", "transactions/add", "parentblock", "coinbase/append",
}
)
// BlockTemplateState houses state that is used in between multiple RPC invocations to
// getBlockTemplate.
type BlockTemplateState struct {
sync.Mutex
context *Context
lastTxUpdate mstime.Time
lastGenerated mstime.Time
virtualParentHashes []*daghash.Hash
minTimestamp mstime.Time
template *mining.BlockTemplate
notifyMap map[string]map[int64]chan struct{}
payAddress util.Address
}
// NewBlockTemplateState returns a new instance of a BlockTemplateState with all internal
// fields initialized and ready to use.
func NewBlockTemplateState(context *Context) *BlockTemplateState {
return &BlockTemplateState{
context: context,
notifyMap: make(map[string]map[int64]chan struct{}),
}
}
// Update updates the block template state
func (bt *BlockTemplateState) Update(payAddress util.Address) error {
generator := bt.context.BlockTemplateGenerator
lastTxUpdate := generator.TxSource().LastUpdated()
if lastTxUpdate.IsZero() {
lastTxUpdate = mstime.Now()
}
// Generate a new block template when the current best block has
// changed or the transactions in the memory pool have been updated and
// it has been at least gbtRegenerateSecond since the last template was
// generated.
var msgBlock *appmessage.MsgBlock
var targetDifficulty string
virtualParentHashes := bt.context.DAG.VirtualParentHashes()
template := bt.template
if template == nil || bt.virtualParentHashes == nil ||
!daghash.AreEqual(bt.virtualParentHashes, virtualParentHashes) ||
bt.payAddress.String() != payAddress.String() ||
(bt.lastTxUpdate != lastTxUpdate &&
mstime.Now().After(bt.lastGenerated.Add(time.Second*
blockTemplateRegenerateSeconds))) {
// Reset the previous best hash the block template was generated
// against so any errors below cause the next invocation to try
// again.
bt.virtualParentHashes = nil
// Create a new block template that has a coinbase which anyone
// can redeem. This is only acceptable because the returned
// block template doesn't include the coinbase, so the caller
// will ultimately create their own coinbase which pays to the
// appropriate address(es).
extraNonce, err := random.Uint64()
if err != nil {
return errors.Wrapf(err, "failed to randomize extra nonce")
}
blockTemplate, err := generator.NewBlockTemplate(payAddress, extraNonce)
if err != nil {
return errors.Wrapf(err, "failed to create new block template")
}
template = blockTemplate
msgBlock = template.Block
targetDifficulty = fmt.Sprintf("%064x", util.CompactToBig(msgBlock.Header.Bits))
// Get the minimum allowed timestamp for the block based on the
// median timestamp of the last several blocks per the DAG
// consensus rules.
minTimestamp := bt.context.DAG.NextBlockMinimumTime()
// Update work state to ensure another block template isn't
// generated until needed.
bt.template = template
bt.lastGenerated = mstime.Now()
bt.lastTxUpdate = lastTxUpdate
bt.virtualParentHashes = virtualParentHashes
bt.minTimestamp = minTimestamp
bt.payAddress = payAddress
log.Debugf("Generated block template (timestamp %s, "+
"target %s, merkle root %s)",
msgBlock.Header.Timestamp, targetDifficulty,
msgBlock.Header.HashMerkleRoot)
// Notify any clients that are long polling about the new
// template.
bt.notifyLongPollers(virtualParentHashes, lastTxUpdate)
} else {
// At this point, there is a saved block template and another
// request for a template was made, but either the available
// transactions haven't change or it hasn't been long enough to
// trigger a new block template to be generated. So, update the
// existing block template.
// Set locals for convenience.
msgBlock = template.Block
targetDifficulty = fmt.Sprintf("%064x",
util.CompactToBig(msgBlock.Header.Bits))
// Update the time of the block template to the current time
// while accounting for the median time of the past several
// blocks per the DAG consensus rules.
err := generator.UpdateBlockTime(msgBlock)
if err != nil {
return errors.Wrapf(err, "failed to update block time")
}
msgBlock.Header.Nonce = 0
log.Debugf("Updated block template (timestamp %s, "+
"target %s)", msgBlock.Header.Timestamp,
targetDifficulty)
}
return nil
}
// Response builds a GetBlockTemplateResponseMessage from the current state
func (bt *BlockTemplateState) Response() (*appmessage.GetBlockTemplateResponseMessage, error) {
dag := bt.context.DAG
// Ensure the timestamps are still in valid range for the template.
// This should really only ever happen if the local clock is changed
// after the template is generated, but it's important to avoid serving
// block templates that will be delayed on other nodes.
template := bt.template
msgBlock := template.Block
header := &msgBlock.Header
adjustedTime := dag.Now()
maxTime := adjustedTime.Add(time.Millisecond * time.Duration(dag.TimestampDeviationTolerance))
if header.Timestamp.After(maxTime) {
errorMessage := &appmessage.GetBlockTemplateResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("The template time is after the "+
"maximum allowed time for a block - template "+
"time %s, maximum time %s", adjustedTime,
maxTime)
return errorMessage, nil
}
// Convert each transaction in the block template to a template result
// transaction. The result does not include the coinbase, so notice
// the adjustments to the various lengths and indices.
numTx := len(msgBlock.Transactions)
transactions := make([]appmessage.GetBlockTemplateTransactionMessage, 0, numTx-1)
txIndex := make(map[daghash.TxID]int64, numTx)
for i, tx := range msgBlock.Transactions {
txID := tx.TxID()
txIndex[*txID] = int64(i)
// Create an array of 1-based indices to transactions that come
// before this one in the transactions list which this one
// depends on. This is necessary since the created block must
// ensure proper ordering of the dependencies. A map is used
// before creating the final array to prevent duplicate entries
// when multiple inputs reference the same transaction.
dependsMap := make(map[int64]struct{})
for _, txIn := range tx.TxIn {
if idx, ok := txIndex[txIn.PreviousOutpoint.TxID]; ok {
dependsMap[idx] = struct{}{}
}
}
depends := make([]int64, 0, len(dependsMap))
for idx := range dependsMap {
depends = append(depends, idx)
}
// Serialize the transaction for later conversion to hex.
txBuf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
if err := tx.Serialize(txBuf); err != nil {
errorMessage := &appmessage.GetBlockTemplateResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Failed to serialize transaction: %s", err)
return errorMessage, nil
}
resultTx := appmessage.GetBlockTemplateTransactionMessage{
Data: hex.EncodeToString(txBuf.Bytes()),
ID: txID.String(),
Depends: depends,
Mass: template.TxMasses[i],
Fee: template.Fees[i],
}
transactions = append(transactions, resultTx)
}
// Generate the block template reply. Note that following mutations are
// implied by the included or omission of fields:
// Including MinTime -> time/decrement
// Omitting CoinbaseTxn -> coinbase, generation
targetDifficulty := fmt.Sprintf("%064x", util.CompactToBig(header.Bits))
longPollID := bt.encodeLongPollID(bt.virtualParentHashes, bt.payAddress, bt.lastGenerated)
// Check whether this node is synced with the rest of of the
// network. There's almost never a good reason to mine on top
// of an unsynced DAG, and miners are generally expected not to
// mine when isSynced is false.
// This is not a straight-up error because the choice of whether
// to mine or not is the responsibility of the miner rather
// than the node's.
isSynced := bt.context.BlockTemplateGenerator.IsSynced()
isConnected := len(bt.context.ProtocolManager.Peers()) > 0
reply := appmessage.GetBlockTemplateResponseMessage{
Bits: strconv.FormatInt(int64(header.Bits), 16),
CurrentTime: header.Timestamp.UnixMilliseconds(),
ParentHashes: daghash.Strings(header.ParentHashes),
MassLimit: appmessage.MaxMassAcceptedByBlock,
Transactions: transactions,
HashMerkleRoot: header.HashMerkleRoot.String(),
AcceptedIDMerkleRoot: header.AcceptedIDMerkleRoot.String(),
UTXOCommitment: header.UTXOCommitment.String(),
Version: header.Version,
LongPollID: longPollID,
TargetDifficulty: targetDifficulty,
MinTime: bt.minTimestamp.UnixMilliseconds(),
MaxTime: maxTime.UnixMilliseconds(),
MutableFields: blockTemplateMutableFields,
NonceRange: blockTemplateNonceRange,
IsSynced: isSynced,
IsConnected: isConnected,
}
return &reply, nil
}
// notifyLongPollers notifies any channels that have been registered to be
// notified when block templates are stale.
//
// This function MUST be called with the state locked.
func (bt *BlockTemplateState) notifyLongPollers(parentHashes []*daghash.Hash, lastGenerated mstime.Time) {
// Notify anything that is waiting for a block template update from
// hashes which are not the current parent hashes.
parentHashesStr := daghash.JoinHashesStrings(parentHashes, "")
for hashesStr, channels := range bt.notifyMap {
if hashesStr != parentHashesStr {
for _, c := range channels {
close(c)
}
delete(bt.notifyMap, hashesStr)
}
}
// Return now if the provided last generated timestamp has not been
// initialized.
if lastGenerated.IsZero() {
return
}
// Return now if there is nothing registered for updates to the current
// best block hash.
channels, ok := bt.notifyMap[parentHashesStr]
if !ok {
return
}
// Notify anything that is waiting for a block template update from a
// block template generated before the most recently generated block
// template.
lastGeneratedUnix := lastGenerated.UnixSeconds()
for lastGen, c := range channels {
if lastGen < lastGeneratedUnix {
close(c)
delete(channels, lastGen)
}
}
// Remove the entry altogether if there are no more registered
// channels.
if len(channels) == 0 {
delete(bt.notifyMap, parentHashesStr)
}
}
// NotifyBlockAdded uses the newly-added block to notify any long poll
// clients with a new block template when their existing block template is
// stale due to the newly added block.
func (bt *BlockTemplateState) NotifyBlockAdded(block *util.Block) {
spawn("BlockTemplateState.NotifyBlockAdded", func() {
bt.Lock()
defer bt.Unlock()
bt.notifyLongPollers(block.MsgBlock().Header.ParentHashes, bt.lastTxUpdate)
})
}
// NotifyMempoolTx uses the new last updated time for the transaction memory
// pool to notify any long poll clients with a new block template when their
// existing block template is stale due to enough time passing and the contents
// of the memory pool changing.
func (bt *BlockTemplateState) NotifyMempoolTx() {
lastUpdated := bt.context.Mempool.LastUpdated()
spawn("BlockTemplateState", func() {
bt.Lock()
defer bt.Unlock()
// No need to notify anything if no block templates have been generated
// yet.
if bt.virtualParentHashes == nil || bt.lastGenerated.IsZero() {
return
}
if mstime.Now().After(bt.lastGenerated.Add(time.Second *
blockTemplateRegenerateSeconds)) {
bt.notifyLongPollers(bt.virtualParentHashes, lastUpdated)
}
})
}
// BlockTemplateOrLongPollChan returns a block template if the
// template identified by the provided long poll ID is stale or
// invalid. Otherwise, it returns a channel that will notify
// when there's a more current template.
func (bt *BlockTemplateState) BlockTemplateOrLongPollChan(longPollID string,
payAddress util.Address) (*appmessage.GetBlockTemplateResponseMessage, chan struct{}, error) {
bt.Lock()
defer bt.Unlock()
if err := bt.Update(payAddress); err != nil {
return nil, nil, err
}
// Just return the current block template if the long poll ID provided by
// the caller is invalid.
parentHashes, lastGenerated, err := bt.decodeLongPollID(longPollID)
if err != nil {
result, err := bt.Response()
if err != nil {
return nil, nil, err
}
return result, nil, nil
}
// Return the block template now if the specific block template
// identified by the long poll ID no longer matches the current block
// template as this means the provided template is stale.
areHashesEqual := daghash.AreEqual(bt.template.Block.Header.ParentHashes, parentHashes)
if !areHashesEqual ||
lastGenerated != bt.lastGenerated.UnixSeconds() {
// Include whether or not it is valid to submit work against the
// old block template depending on whether or not a solution has
// already been found and added to the block DAG.
result, err := bt.Response()
if err != nil {
return nil, nil, err
}
return result, nil, nil
}
// Register the parent hashes and last generated time for notifications
// Get a channel that will be notified when the template associated with
// the provided ID is stale and a new block template should be returned to
// the caller.
longPollChan := bt.templateUpdateChan(parentHashes, lastGenerated)
return nil, longPollChan, nil
}
// templateUpdateChan returns a channel that will be closed once the block
// template associated with the passed parent hashes and last generated time
// is stale. The function will return existing channels for duplicate
// parameters which allows multiple clients to wait for the same block template
// without requiring a different channel for each client.
//
// This function MUST be called with the state locked.
func (bt *BlockTemplateState) templateUpdateChan(parentHashes []*daghash.Hash, lastGenerated int64) chan struct{} {
parentHashesStr := daghash.JoinHashesStrings(parentHashes, "")
// Either get the current list of channels waiting for updates about
// changes to block template for the parent hashes or create a new one.
channels, ok := bt.notifyMap[parentHashesStr]
if !ok {
m := make(map[int64]chan struct{})
bt.notifyMap[parentHashesStr] = m
channels = m
}
// Get the current channel associated with the time the block template
// was last generated or create a new one.
c, ok := channels[lastGenerated]
if !ok {
c = make(chan struct{})
channels[lastGenerated] = c
}
return c
}
// encodeLongPollID encodes the passed details into an ID that can be used to
// uniquely identify a block template.
func (bt *BlockTemplateState) encodeLongPollID(parentHashes []*daghash.Hash, miningAddress util.Address, lastGenerated mstime.Time) string {
return fmt.Sprintf("%s-%s-%d", daghash.JoinHashesStrings(parentHashes, ""), miningAddress, lastGenerated.UnixSeconds())
}
// decodeLongPollID decodes an ID that is used to uniquely identify a block
// template. This is mainly used as a mechanism to track when to update clients
// that are using long polling for block templates. The ID consists of the
// parent blocks hashes for the associated template and the time the associated
// template was generated.
func (bt *BlockTemplateState) decodeLongPollID(longPollID string) ([]*daghash.Hash, int64, error) {
fields := strings.Split(longPollID, "-")
if len(fields) != 2 {
return nil, 0, errors.New("decodeLongPollID: invalid number of fields")
}
parentHashesStr := fields[0]
if len(parentHashesStr)%daghash.HashSize != 0 {
return nil, 0, errors.New("decodeLongPollID: invalid parent hashes format")
}
numberOfHashes := len(parentHashesStr) / daghash.HashSize
parentHashes := make([]*daghash.Hash, 0, numberOfHashes)
for i := 0; i < len(parentHashesStr); i += daghash.HashSize {
hash, err := daghash.NewHashFromStr(parentHashesStr[i : i+daghash.HashSize])
if err != nil {
return nil, 0, errors.Errorf("decodeLongPollID: NewHashFromStr: %s", err)
}
parentHashes = append(parentHashes, hash)
}
lastGenerated, err := strconv.ParseInt(fields[1], 10, 64)
if err != nil {
return nil, 0, errors.Errorf("decodeLongPollID: Cannot parse timestamp %s: %s", fields[1], err)
}
return parentHashes, lastGenerated, nil
}

View File

@@ -1,40 +0,0 @@
package rpccontext
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
// CollectChainBlocks creates a slice of chain blocks from the given hashes
func (ctx *Context) CollectChainBlocks(hashes []*daghash.Hash) ([]*appmessage.ChainBlock, error) {
chainBlocks := make([]*appmessage.ChainBlock, 0, len(hashes))
for _, hash := range hashes {
acceptanceData, err := ctx.AcceptanceIndex.TxsAcceptanceData(hash)
if err != nil {
return nil, errors.Errorf("could not retrieve acceptance data for block %s", hash)
}
acceptedBlocks := make([]*appmessage.AcceptedBlock, 0, len(acceptanceData))
for _, blockAcceptanceData := range acceptanceData {
acceptedTxIds := make([]string, 0, len(blockAcceptanceData.TxAcceptanceData))
for _, txAcceptanceData := range blockAcceptanceData.TxAcceptanceData {
if txAcceptanceData.IsAccepted {
acceptedTxIds = append(acceptedTxIds, txAcceptanceData.Tx.ID().String())
}
}
acceptedBlock := &appmessage.AcceptedBlock{
Hash: blockAcceptanceData.BlockHash.String(),
AcceptedTxIDs: acceptedTxIds,
}
acceptedBlocks = append(acceptedBlocks, acceptedBlock)
}
chainBlock := &appmessage.ChainBlock{
Hash: hash.String(),
AcceptedBlocks: acceptedBlocks,
}
chainBlocks = append(chainBlocks, chainBlock)
}
return chainBlocks, nil
}

View File

@@ -1,59 +0,0 @@
package rpccontext
import (
"github.com/kaspanet/kaspad/app/protocol"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
"github.com/kaspanet/kaspad/domain/mempool"
"github.com/kaspanet/kaspad/domain/mining"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
)
// Context represents the RPC context
type Context struct {
Config *config.Config
NetAdapter *netadapter.NetAdapter
DAG *blockdag.BlockDAG
ProtocolManager *protocol.Manager
ConnectionManager *connmanager.ConnectionManager
BlockTemplateGenerator *mining.BlkTmplGenerator
Mempool *mempool.TxPool
AddressManager *addressmanager.AddressManager
AcceptanceIndex *indexers.AcceptanceIndex
ShutDownChan chan<- struct{}
BlockTemplateState *BlockTemplateState
NotificationManager *NotificationManager
}
// NewContext creates a new RPC context
func NewContext(cfg *config.Config,
netAdapter *netadapter.NetAdapter,
dag *blockdag.BlockDAG,
protocolManager *protocol.Manager,
connectionManager *connmanager.ConnectionManager,
blockTemplateGenerator *mining.BlkTmplGenerator,
mempool *mempool.TxPool,
addressManager *addressmanager.AddressManager,
acceptanceIndex *indexers.AcceptanceIndex,
shutDownChan chan<- struct{}) *Context {
context := &Context{
Config: cfg,
NetAdapter: netAdapter,
DAG: dag,
ProtocolManager: protocolManager,
ConnectionManager: connectionManager,
BlockTemplateGenerator: blockTemplateGenerator,
Mempool: mempool,
AddressManager: addressManager,
AcceptanceIndex: acceptanceIndex,
ShutDownChan: shutDownChan,
}
context.BlockTemplateState = NewBlockTemplateState(context)
context.NotificationManager = NewNotificationManager()
return context
}

View File

@@ -1,9 +0,0 @@
package rpccontext
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/panics"
)
var log, _ = logger.Get(logger.SubsystemTags.RPCS)
var spawn = panics.GoroutineWrapperFunc(log)

View File

@@ -1,155 +0,0 @@
package rpccontext
import (
"github.com/kaspanet/kaspad/app/appmessage"
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/pkg/errors"
"sync"
)
// NotificationManager manages notifications for the RPC
type NotificationManager struct {
sync.RWMutex
listeners map[*routerpkg.Router]*NotificationListener
}
// NotificationListener represents a registered RPC notification listener
type NotificationListener struct {
propagateBlockAddedNotifications bool
propagateChainChangedNotifications bool
propagateFinalityConflictNotifications bool
propagateFinalityConflictResolvedNotifications bool
}
// NewNotificationManager creates a new NotificationManager
func NewNotificationManager() *NotificationManager {
return &NotificationManager{
listeners: make(map[*routerpkg.Router]*NotificationListener),
}
}
// AddListener registers a listener with the given router
func (nm *NotificationManager) AddListener(router *routerpkg.Router) {
nm.Lock()
defer nm.Unlock()
listener := newNotificationListener()
nm.listeners[router] = listener
}
// RemoveListener unregisters the given router
func (nm *NotificationManager) RemoveListener(router *routerpkg.Router) {
nm.Lock()
defer nm.Unlock()
delete(nm.listeners, router)
}
// Listener retrieves the listener registered with the given router
func (nm *NotificationManager) Listener(router *routerpkg.Router) (*NotificationListener, error) {
nm.RLock()
defer nm.RUnlock()
listener, ok := nm.listeners[router]
if !ok {
return nil, errors.Errorf("listener not found")
}
return listener, nil
}
// NotifyBlockAdded notifies the notification manager that a block has been added to the DAG
func (nm *NotificationManager) NotifyBlockAdded(notification *appmessage.BlockAddedNotificationMessage) error {
nm.RLock()
defer nm.RUnlock()
for router, listener := range nm.listeners {
if listener.propagateBlockAddedNotifications {
err := router.OutgoingRoute().Enqueue(notification)
if err != nil {
return err
}
}
}
return nil
}
// NotifyChainChanged notifies the notification manager that the DAG's selected parent chain has changed
func (nm *NotificationManager) NotifyChainChanged(notification *appmessage.ChainChangedNotificationMessage) error {
nm.RLock()
defer nm.RUnlock()
for router, listener := range nm.listeners {
if listener.propagateChainChangedNotifications {
err := router.OutgoingRoute().Enqueue(notification)
if err != nil {
return err
}
}
}
return nil
}
// NotifyFinalityConflict notifies the notification manager that there's a finality conflict in the DAG
func (nm *NotificationManager) NotifyFinalityConflict(notification *appmessage.FinalityConflictNotificationMessage) error {
nm.RLock()
defer nm.RUnlock()
for router, listener := range nm.listeners {
if listener.propagateFinalityConflictNotifications {
err := router.OutgoingRoute().Enqueue(notification)
if err != nil {
return err
}
}
}
return nil
}
// NotifyFinalityConflictResolved notifies the notification manager that a finality conflict in the DAG has been resolved
func (nm *NotificationManager) NotifyFinalityConflictResolved(notification *appmessage.FinalityConflictResolvedNotificationMessage) error {
nm.RLock()
defer nm.RUnlock()
for router, listener := range nm.listeners {
if listener.propagateFinalityConflictResolvedNotifications {
err := router.OutgoingRoute().Enqueue(notification)
if err != nil {
return err
}
}
}
return nil
}
func newNotificationListener() *NotificationListener {
return &NotificationListener{
propagateBlockAddedNotifications: false,
propagateChainChangedNotifications: false,
propagateFinalityConflictNotifications: false,
propagateFinalityConflictResolvedNotifications: false,
}
}
// PropagateBlockAddedNotifications instructs the listener to send block added notifications
// to the remote listener
func (nl *NotificationListener) PropagateBlockAddedNotifications() {
nl.propagateBlockAddedNotifications = true
}
// PropagateChainChangedNotifications instructs the listener to send chain changed notifications
// to the remote listener
func (nl *NotificationListener) PropagateChainChangedNotifications() {
nl.propagateChainChangedNotifications = true
}
// PropagateFinalityConflictNotifications instructs the listener to send finality conflict notifications
// to the remote listener
func (nl *NotificationListener) PropagateFinalityConflictNotifications() {
nl.propagateFinalityConflictNotifications = true
}
// PropagateFinalityConflictResolvedNotifications instructs the listener to send finality conflict resolved notifications
// to the remote listener
func (nl *NotificationListener) PropagateFinalityConflictResolvedNotifications() {
nl.propagateFinalityConflictResolvedNotifications = true
}

View File

@@ -1,248 +0,0 @@
package rpccontext
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/pointers"
"math/big"
"strconv"
)
// BuildBlockVerboseData builds a BlockVerboseData from the given block.
// This method must be called with the DAG lock held for reads
func (ctx *Context) BuildBlockVerboseData(block *util.Block, includeTransactionVerboseData bool) (*appmessage.BlockVerboseData, error) {
hash := block.Hash()
params := ctx.DAG.Params
blockHeader := block.MsgBlock().Header
blockBlueScore, err := ctx.DAG.BlueScoreByBlockHash(hash)
if err != nil {
return nil, err
}
// Get the hashes for the next blocks unless there are none.
childHashes, err := ctx.DAG.ChildHashesByHash(hash)
if err != nil {
return nil, err
}
blockConfirmations, err := ctx.DAG.BlockConfirmationsByHashNoLock(hash)
if err != nil {
return nil, err
}
selectedParentHash, err := ctx.DAG.SelectedParentHash(hash)
if err != nil {
return nil, err
}
selectedParentHashStr := ""
if selectedParentHash != nil {
selectedParentHashStr = selectedParentHash.String()
}
isChainBlock, err := ctx.DAG.IsInSelectedParentChain(hash)
if err != nil {
return nil, err
}
acceptedBlockHashes, err := ctx.DAG.BluesByBlockHash(hash)
if err != nil {
return nil, err
}
result := &appmessage.BlockVerboseData{
Hash: hash.String(),
Version: blockHeader.Version,
VersionHex: fmt.Sprintf("%08x", blockHeader.Version),
HashMerkleRoot: blockHeader.HashMerkleRoot.String(),
AcceptedIDMerkleRoot: blockHeader.AcceptedIDMerkleRoot.String(),
UTXOCommitment: blockHeader.UTXOCommitment.String(),
ParentHashes: daghash.Strings(blockHeader.ParentHashes),
SelectedParentHash: selectedParentHashStr,
Nonce: blockHeader.Nonce,
Time: blockHeader.Timestamp.UnixMilliseconds(),
Confirmations: blockConfirmations,
BlueScore: blockBlueScore,
IsChainBlock: isChainBlock,
Size: int32(block.MsgBlock().SerializeSize()),
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
Difficulty: ctx.GetDifficultyRatio(blockHeader.Bits, params),
ChildHashes: daghash.Strings(childHashes),
AcceptedBlockHashes: daghash.Strings(acceptedBlockHashes),
}
transactions := block.Transactions()
txIDs := make([]string, len(transactions))
for i, tx := range transactions {
txIDs[i] = tx.ID().String()
}
result.TxIDs = txIDs
if includeTransactionVerboseData {
transactions := block.Transactions()
transactionVerboseData := make([]*appmessage.TransactionVerboseData, len(transactions))
for i, tx := range transactions {
data, err := ctx.BuildTransactionVerboseData(tx.MsgTx(), tx.ID().String(),
&blockHeader, hash.String(), nil, false)
if err != nil {
return nil, err
}
transactionVerboseData[i] = data
}
result.TransactionVerboseData = transactionVerboseData
}
return result, nil
}
// GetDifficultyRatio returns the proof-of-work difficulty as a multiple of the
// minimum difficulty using the passed bits field from the header of a block.
func (ctx *Context) GetDifficultyRatio(bits uint32, params *dagconfig.Params) float64 {
// The minimum difficulty is the max possible proof-of-work limit bits
// converted back to a number. Note this is not the same as the proof of
// work limit directly because the block difficulty is encoded in a block
// with the compact form which loses precision.
target := util.CompactToBig(bits)
difficulty := new(big.Rat).SetFrac(params.PowMax, target)
outString := difficulty.FloatString(8)
diff, err := strconv.ParseFloat(outString, 64)
if err != nil {
log.Errorf("Cannot get difficulty: %s", err)
return 0
}
return diff
}
// BuildTransactionVerboseData builds a TransactionVerboseData from
// the given parameters
func (ctx *Context) BuildTransactionVerboseData(mtx *appmessage.MsgTx,
txID string, blockHeader *appmessage.BlockHeader, blockHash string,
acceptingBlock *daghash.Hash, isInMempool bool) (*appmessage.TransactionVerboseData, error) {
mtxHex, err := msgTxToHex(mtx)
if err != nil {
return nil, err
}
var payloadHash string
if mtx.PayloadHash != nil {
payloadHash = mtx.PayloadHash.String()
}
txReply := &appmessage.TransactionVerboseData{
Hex: mtxHex,
TxID: txID,
Hash: mtx.TxHash().String(),
Size: int32(mtx.SerializeSize()),
TransactionVerboseInputs: ctx.buildTransactionVerboseInputs(mtx),
TransactionVerboseOutputs: ctx.buildTransactionVerboseOutputs(mtx, nil),
Version: mtx.Version,
LockTime: mtx.LockTime,
SubnetworkID: mtx.SubnetworkID.String(),
Gas: mtx.Gas,
PayloadHash: payloadHash,
Payload: hex.EncodeToString(mtx.Payload),
}
if blockHeader != nil {
txReply.Time = uint64(blockHeader.Timestamp.UnixMilliseconds())
txReply.BlockTime = uint64(blockHeader.Timestamp.UnixMilliseconds())
txReply.BlockHash = blockHash
}
txReply.IsInMempool = isInMempool
if acceptingBlock != nil {
txReply.AcceptedBy = acceptingBlock.String()
}
return txReply, nil
}
// msgTxToHex serializes a transaction using the latest protocol version and
// returns a hex-encoded string of the result.
func msgTxToHex(msgTx *appmessage.MsgTx) (string, error) {
var buf bytes.Buffer
err := msgTx.KaspaEncode(&buf, 0)
if err != nil {
return "", err
}
return hex.EncodeToString(buf.Bytes()), nil
}
func (ctx *Context) buildTransactionVerboseInputs(mtx *appmessage.MsgTx) []*appmessage.TransactionVerboseInput {
inputs := make([]*appmessage.TransactionVerboseInput, len(mtx.TxIn))
for i, txIn := range mtx.TxIn {
// The disassembled string will contain [error] inline
// if the script doesn't fully parse, so ignore the
// error here.
disbuf, _ := txscript.DisasmString(txIn.SignatureScript)
input := &appmessage.TransactionVerboseInput{}
input.TxID = txIn.PreviousOutpoint.TxID.String()
input.OutputIndex = txIn.PreviousOutpoint.Index
input.Sequence = txIn.Sequence
input.ScriptSig = &appmessage.ScriptSig{
Asm: disbuf,
Hex: hex.EncodeToString(txIn.SignatureScript),
}
inputs[i] = input
}
return inputs
}
// buildTransactionVerboseOutputs returns a slice of JSON objects for the outputs of the passed
// transaction.
func (ctx *Context) buildTransactionVerboseOutputs(mtx *appmessage.MsgTx, filterAddrMap map[string]struct{}) []*appmessage.TransactionVerboseOutput {
outputs := make([]*appmessage.TransactionVerboseOutput, len(mtx.TxOut))
for i, v := range mtx.TxOut {
// The disassembled string will contain [error] inline if the
// script doesn't fully parse, so ignore the error here.
disbuf, _ := txscript.DisasmString(v.ScriptPubKey)
// Ignore the error here since an error means the script
// couldn't parse and there is no additional information about
// it anyways.
scriptClass, addr, _ := txscript.ExtractScriptPubKeyAddress(
v.ScriptPubKey, ctx.DAG.Params)
// Encode the addresses while checking if the address passes the
// filter when needed.
passesFilter := len(filterAddrMap) == 0
var encodedAddr string
if addr != nil {
encodedAddr = *pointers.String(addr.EncodeAddress())
// If the filter doesn't already pass, make it pass if
// the address exists in the filter.
if _, exists := filterAddrMap[encodedAddr]; exists {
passesFilter = true
}
}
if !passesFilter {
continue
}
output := &appmessage.TransactionVerboseOutput{}
output.Index = uint32(i)
output.Value = v.Value
output.ScriptPubKey = &appmessage.ScriptPubKeyResult{
Address: encodedAddr,
Asm: disbuf,
Hex: hex.EncodeToString(v.ScriptPubKey),
Type: scriptClass.String(),
}
outputs[i] = output
}
return outputs
}

View File

@@ -1,24 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/network"
)
// HandleAddPeer handles the respectively named RPC command
func HandleAddPeer(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
AddPeerRequest := request.(*appmessage.AddPeerRequestMessage)
address, err := network.NormalizeAddress(AddPeerRequest.Address, context.DAG.Params.DefaultPort)
if err != nil {
errorMessage := &appmessage.AddPeerResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse address: %s", err)
return errorMessage, nil
}
context.ConnectionManager.AddConnectionRequest(address, AddPeerRequest.IsPermanent)
response := appmessage.NewAddPeerResponseMessage()
return response, nil
}

View File

@@ -1,98 +0,0 @@
package rpchandlers
import (
"bufio"
"bytes"
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
// HandleGetBlock handles the respectively named RPC command
func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getBlockRequest := request.(*appmessage.GetBlockRequestMessage)
// Load the raw block bytes from the database.
hash, err := daghash.NewHashFromStr(getBlockRequest.Hash)
if err != nil {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Hash could not be parsed: %s", err)
return errorMessage, nil
}
context.DAG.RLock()
defer context.DAG.RUnlock()
if context.DAG.IsKnownInvalid(hash) {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s is known to be invalid", hash)
return errorMessage, nil
}
if context.DAG.IsKnownOrphan(hash) {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s is an orphan", hash)
return errorMessage, nil
}
block, err := context.DAG.BlockByHash(hash)
if err != nil {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s not found", hash)
return errorMessage, nil
}
blockBytes, err := block.Bytes()
if err != nil {
return nil, err
}
// Handle partial blocks
if getBlockRequest.SubnetworkID != "" {
requestSubnetworkID, err := subnetworkid.NewFromStr(getBlockRequest.SubnetworkID)
if err != nil {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("SubnetworkID could not be parsed: %s", err)
return errorMessage, nil
}
nodeSubnetworkID := context.Config.SubnetworkID
if requestSubnetworkID != nil {
if nodeSubnetworkID != nil {
if !nodeSubnetworkID.IsEqual(requestSubnetworkID) {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("subnetwork %s does not match this partial node",
getBlockRequest.SubnetworkID)
return errorMessage, nil
}
// nothing to do - partial node stores partial blocks
} else {
// Deserialize the block.
msgBlock := block.MsgBlock()
msgBlock.ConvertToPartial(requestSubnetworkID)
var b bytes.Buffer
err := msgBlock.Serialize(bufio.NewWriter(&b))
if err != nil {
return nil, err
}
blockBytes = b.Bytes()
}
}
}
response := appmessage.NewGetBlockResponseMessage()
if getBlockRequest.IncludeBlockHex {
response.BlockHex = hex.EncodeToString(blockBytes)
}
if getBlockRequest.IncludeBlockVerboseData {
blockVerboseData, err := context.BuildBlockVerboseData(block, getBlockRequest.IncludeTransactionVerboseData)
if err != nil {
return nil, err
}
response.BlockVerboseData = blockVerboseData
}
return response, nil
}

View File

@@ -1,13 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetBlockCount handles the respectively named RPC command
func HandleGetBlockCount(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
response := appmessage.NewGetBlockCountResponseMessage(context.DAG.BlockCount())
return response, nil
}

View File

@@ -1,23 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
)
// HandleGetBlockDAGInfo handles the respectively named RPC command
func HandleGetBlockDAGInfo(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
dag := context.DAG
params := dag.Params
response := appmessage.NewGetBlockDAGInfoResponseMessage()
response.NetworkName = params.Name
response.BlockCount = dag.BlockCount()
response.TipHashes = daghash.Strings(dag.TipHashes())
response.VirtualParentHashes = daghash.Strings(dag.VirtualParentHashes())
response.Difficulty = context.GetDifficultyRatio(dag.CurrentBits(), params)
response.PastMedianTime = dag.CalcPastMedianTime().UnixMilliseconds()
return response, nil
}

View File

@@ -1,86 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
)
// HandleGetBlockTemplate handles the respectively named RPC command
func HandleGetBlockTemplate(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getBlockTemplateRequest := request.(*appmessage.GetBlockTemplateRequestMessage)
payAddress, err := util.DecodeAddress(getBlockTemplateRequest.PayAddress, context.DAG.Params.Prefix)
if err != nil {
errorMessage := &appmessage.GetBlockTemplateResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not decode address: %s", err)
return errorMessage, nil
}
// When a long poll ID was provided, this is a long poll request by the
// client to be notified when block template referenced by the ID should
// be replaced with a new one.
if getBlockTemplateRequest.LongPollID != "" {
return handleGetBlockTemplateLongPoll(context, getBlockTemplateRequest.LongPollID, payAddress)
}
// Protect concurrent access when updating block templates.
context.BlockTemplateState.Lock()
defer context.BlockTemplateState.Unlock()
// Get and return a block template. A new block template will be
// generated when the current best block has changed or the transactions
// in the memory pool have been updated and it has been at least five
// seconds since the last template was generated. Otherwise, the
// timestamp for the existing block template is updated (and possibly
// the difficulty on testnet per the consesus rules).
err = context.BlockTemplateState.Update(payAddress)
if err != nil {
return nil, err
}
return context.BlockTemplateState.Response()
}
// handleGetBlockTemplateLongPoll is a helper for handleGetBlockTemplateRequest
// which deals with handling long polling for block templates. When a caller
// sends a request with a long poll ID that was previously returned, a response
// is not sent until the caller should stop working on the previous block
// template in favor of the new one. In particular, this is the case when the
// old block template is no longer valid due to a solution already being found
// and added to the block DAG, or new transactions have shown up and some time
// has passed without finding a solution.
func handleGetBlockTemplateLongPoll(context *rpccontext.Context, longPollID string,
payAddress util.Address) (*appmessage.GetBlockTemplateResponseMessage, error) {
state := context.BlockTemplateState
result, longPollChan, err := state.BlockTemplateOrLongPollChan(longPollID, payAddress)
if err != nil {
return nil, err
}
if result != nil {
return result, nil
}
// Wait until signal received to send the reply.
<-longPollChan
// Get the lastest block template
state.Lock()
defer state.Unlock()
if err := state.Update(payAddress); err != nil {
return nil, err
}
// Include whether or not it is valid to submit work against the old
// block template depending on whether or not a solution has already
// been found and added to the block DAG.
result, err = state.Response()
if err != nil {
return nil, err
}
return result, nil
}

View File

@@ -1,118 +0,0 @@
package rpchandlers
import (
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
)
const (
// maxBlocksInGetBlocksResponse is the max amount of blocks that are
// allowed in a GetBlocksResult.
maxBlocksInGetBlocksResponse = 100
)
// HandleGetBlocks handles the respectively named RPC command
func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getBlocksRequest := request.(*appmessage.GetBlocksRequestMessage)
var lowHash *daghash.Hash
if getBlocksRequest.LowHash != "" {
lowHash = &daghash.Hash{}
err := daghash.Decode(lowHash, getBlocksRequest.LowHash)
if err != nil {
errorMessage := &appmessage.GetBlocksResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse lowHash: %s", err)
return errorMessage, nil
}
}
context.DAG.RLock()
defer context.DAG.RUnlock()
// If lowHash is not in the DAG, there's nothing to do; return an error.
if lowHash != nil && !context.DAG.IsKnownBlock(lowHash) {
errorMessage := &appmessage.GetBlocksResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s not found in DAG", lowHash)
return errorMessage, nil
}
// Retrieve the block hashes.
blockHashes, err := context.DAG.BlockHashesFrom(lowHash, maxBlocksInGetBlocksResponse)
if err != nil {
return nil, err
}
// Convert the hashes to strings
hashes := make([]string, len(blockHashes))
for i, blockHash := range blockHashes {
hashes[i] = blockHash.String()
}
// Include more data if requested
var blockHexes []string
var blockVerboseData []*appmessage.BlockVerboseData
if getBlocksRequest.IncludeBlockHexes || getBlocksRequest.IncludeBlockVerboseData {
blockBytesSlice, err := hashesToBlockBytes(context, blockHashes)
if err != nil {
return nil, err
}
if getBlocksRequest.IncludeBlockHexes {
blockHexes = blockBytesToStrings(blockBytesSlice)
}
if getBlocksRequest.IncludeBlockVerboseData {
data, err := blockBytesToBlockVerboseResults(context, blockBytesSlice, getBlocksRequest.IncludeBlockVerboseData)
if err != nil {
return nil, err
}
blockVerboseData = data
}
}
response := appmessage.NewGetBlocksResponseMessage(hashes, blockHexes, blockVerboseData)
return response, nil
}
func hashesToBlockBytes(context *rpccontext.Context, hashes []*daghash.Hash) ([][]byte, error) {
blocks := make([][]byte, len(hashes))
for i, hash := range hashes {
block, err := context.DAG.BlockByHash(hash)
if err != nil {
return nil, err
}
blockBytes, err := block.Bytes()
if err != nil {
return nil, err
}
blocks[i] = blockBytes
}
return blocks, nil
}
func blockBytesToStrings(blockBytesSlice [][]byte) []string {
rawBlocks := make([]string, len(blockBytesSlice))
for i, blockBytes := range blockBytesSlice {
rawBlocks[i] = hex.EncodeToString(blockBytes)
}
return rawBlocks
}
func blockBytesToBlockVerboseResults(context *rpccontext.Context, blockBytesSlice [][]byte,
includeTransactionVerboseData bool) ([]*appmessage.BlockVerboseData, error) {
verboseBlocks := make([]*appmessage.BlockVerboseData, len(blockBytesSlice))
for i, blockBytes := range blockBytesSlice {
block, err := util.NewBlockFromBytes(blockBytes)
if err != nil {
return nil, err
}
getBlockVerboseResult, err := context.BuildBlockVerboseData(block, includeTransactionVerboseData)
if err != nil {
return nil, err
}
verboseBlocks[i] = getBlockVerboseResult
}
return verboseBlocks, nil
}

View File

@@ -1,106 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
const (
// maxBlocksInGetChainFromBlockResponse is the max amount of blocks that
// are allowed in a GetChainFromBlockResponse.
maxBlocksInGetChainFromBlockResponse = 1000
)
// HandleGetChainFromBlock handles the respectively named RPC command
func HandleGetChainFromBlock(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getChainFromBlockRequest := request.(*appmessage.GetChainFromBlockRequestMessage)
if context.AcceptanceIndex == nil {
errorMessage := &appmessage.GetChainFromBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("The acceptance index must be " +
"enabled to get the selected parent chain " +
"(specify --acceptanceindex)")
return errorMessage, nil
}
var startHash *daghash.Hash
if getChainFromBlockRequest.StartHash != "" {
startHash = &daghash.Hash{}
err := daghash.Decode(startHash, getChainFromBlockRequest.StartHash)
if err != nil {
errorMessage := &appmessage.GetChainFromBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse startHash: %s", err)
return errorMessage, nil
}
}
context.DAG.RLock()
defer context.DAG.RUnlock()
// If startHash is not in the selected parent chain, there's nothing
// to do; return an error.
if startHash != nil && !context.DAG.IsInDAG(startHash) {
errorMessage := &appmessage.GetChainFromBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s not found in the DAG", startHash)
return errorMessage, nil
}
// Retrieve the selected parent chain.
removedChainHashes, addedChainHashes, err := context.DAG.SelectedParentChain(startHash)
if err != nil {
return nil, err
}
// Limit the amount of blocks in the response
if len(addedChainHashes) > maxBlocksInGetChainFromBlockResponse {
addedChainHashes = addedChainHashes[:maxBlocksInGetChainFromBlockResponse]
}
// Collect addedChainBlocks.
addedChainBlocks, err := context.CollectChainBlocks(addedChainHashes)
if err != nil {
errorMessage := &appmessage.GetChainFromBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not collect chain blocks: %s", err)
return errorMessage, nil
}
// Collect removedHashes.
removedHashes := make([]string, len(removedChainHashes))
for i, hash := range removedChainHashes {
removedHashes[i] = hash.String()
}
// If the user specified to include the blocks, collect them as well.
var blockVerboseData []*appmessage.BlockVerboseData
if getChainFromBlockRequest.IncludeBlockVerboseData {
data, err := hashesToBlockVerboseData(context, addedChainHashes)
if err != nil {
return nil, err
}
blockVerboseData = data
}
response := appmessage.NewGetChainFromBlockResponseMessage(removedHashes, addedChainBlocks, blockVerboseData)
return response, nil
}
// hashesToBlockVerboseData takes block hashes and returns their
// correspondent block verbose.
func hashesToBlockVerboseData(context *rpccontext.Context, hashes []*daghash.Hash) ([]*appmessage.BlockVerboseData, error) {
getBlockVerboseResults := make([]*appmessage.BlockVerboseData, 0, len(hashes))
for _, blockHash := range hashes {
block, err := context.DAG.BlockByHash(blockHash)
if err != nil {
return nil, errors.Errorf("could not retrieve block %s.", blockHash)
}
getBlockVerboseResult, err := context.BuildBlockVerboseData(block, false)
if err != nil {
return nil, errors.Wrapf(err, "could not build getBlockVerboseResult for block %s", blockHash)
}
getBlockVerboseResults = append(getBlockVerboseResults, getBlockVerboseResult)
}
return getBlockVerboseResults, nil
}

View File

@@ -1,31 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetConnectedPeerInfo handles the respectively named RPC command
func HandleGetConnectedPeerInfo(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
peers := context.ProtocolManager.Peers()
ibdPeer := context.ProtocolManager.IBDPeer()
infos := make([]*appmessage.GetConnectedPeerInfoMessage, 0, len(peers))
for _, peer := range peers {
info := &appmessage.GetConnectedPeerInfoMessage{
ID: peer.ID().String(),
Address: peer.Address(),
LastPingDuration: peer.LastPingDuration().Milliseconds(),
SelectedTipHash: peer.SelectedTipHash().String(),
IsSyncNode: peer == ibdPeer,
IsOutbound: peer.IsOutbound(),
TimeOffset: peer.TimeOffset().Milliseconds(),
UserAgent: peer.UserAgent(),
AdvertisedProtocolVersion: peer.AdvertisedProtocolVersion(),
TimeConnected: peer.TimeConnected().Milliseconds(),
}
infos = append(infos, info)
}
response := appmessage.NewGetConnectedPeerInfoResponseMessage(infos)
return response, nil
}

View File

@@ -1,13 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetCurrentNetwork handles the respectively named RPC command
func HandleGetCurrentNetwork(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
response := appmessage.NewGetCurrentNetworkResponseMessage(context.DAG.Params.Net.String())
return response, nil
}

View File

@@ -1,52 +0,0 @@
package rpchandlers
import (
"bytes"
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
)
// HandleGetHeaders handles the respectively named RPC command
func HandleGetHeaders(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getHeadersRequest := request.(*appmessage.GetHeadersRequestMessage)
dag := context.DAG
var startHash *daghash.Hash
if getHeadersRequest.StartHash != "" {
var err error
startHash, err = daghash.NewHashFromStr(getHeadersRequest.StartHash)
if err != nil {
errorMessage := &appmessage.GetHeadersResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Start hash could not be parsed: %s", err)
return errorMessage, nil
}
}
const getHeadersDefaultLimit uint64 = 2000
limit := getHeadersDefaultLimit
if getHeadersRequest.Limit != 0 {
limit = getHeadersRequest.Limit
}
headers, err := dag.GetHeaders(startHash, limit, getHeadersRequest.IsAscending)
if err != nil {
errorMessage := &appmessage.GetHeadersResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Error getting the headers: %s", err)
return errorMessage, nil
}
headersHex := make([]string, len(headers))
var buf bytes.Buffer
for i, header := range headers {
err := header.Serialize(&buf)
if err != nil {
return nil, err
}
headersHex[i] = hex.EncodeToString(buf.Bytes())
buf.Reset()
}
return appmessage.NewGetHeadersResponseMessage(headersHex), nil
}

View File

@@ -1,25 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetMempoolEntries handles the respectively named RPC command
func HandleGetMempoolEntries(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
txDescs := context.Mempool.TxDescs()
entries := make([]*appmessage.MempoolEntry, len(txDescs))
for i, txDesc := range txDescs {
transactionVerboseData, err := context.BuildTransactionVerboseData(txDesc.Tx.MsgTx(), txDesc.Tx.ID().String(),
nil, "", nil, true)
if err != nil {
return nil, err
}
entries[i] = &appmessage.MempoolEntry{
Fee: txDesc.Fee,
TransactionVerboseData: transactionVerboseData,
}
}
return appmessage.NewGetMempoolEntriesResponseMessage(entries), nil
}

View File

@@ -1,35 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
)
// HandleGetMempoolEntry handles the respectively named RPC command
func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getMempoolEntryRequest := request.(*appmessage.GetMempoolEntryRequestMessage)
txID, err := daghash.NewTxIDFromStr(getMempoolEntryRequest.TxID)
if err != nil {
errorMessage := &appmessage.GetMempoolEntryResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse txId: %s", err)
return errorMessage, nil
}
txDesc, ok := context.Mempool.FetchTxDesc(txID)
if !ok {
errorMessage := &appmessage.GetMempoolEntryResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("transaction is not in the pool")
return errorMessage, nil
}
transactionVerboseData, err := context.BuildTransactionVerboseData(txDesc.Tx.MsgTx(), txID.String(),
nil, "", nil, true)
if err != nil {
return nil, err
}
response := appmessage.NewGetMempoolEntryResponseMessage(txDesc.Fee, transactionVerboseData)
return response, nil
}

View File

@@ -1,30 +0,0 @@
package rpchandlers
import (
"net"
"strconv"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetPeerAddresses handles the respectively named RPC command
func HandleGetPeerAddresses(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
netAddresses := context.AddressManager.Addresses()
addressMessages := make([]*appmessage.GetPeerAddressesKnownAddressMessage, len(netAddresses))
for i, netAddress := range netAddresses {
addressWithPort := net.JoinHostPort(netAddress.IP.String(), strconv.FormatUint(uint64(netAddress.Port), 10))
addressMessages[i] = &appmessage.GetPeerAddressesKnownAddressMessage{Addr: addressWithPort}
}
bannedAddresses := context.AddressManager.BannedAddresses()
bannedAddressMessages := make([]*appmessage.GetPeerAddressesKnownAddressMessage, len(bannedAddresses))
for i, netAddress := range bannedAddresses {
addressWithPort := net.JoinHostPort(netAddress.IP.String(), strconv.FormatUint(uint64(netAddress.Port), 10))
bannedAddressMessages[i] = &appmessage.GetPeerAddressesKnownAddressMessage{Addr: addressWithPort}
}
response := appmessage.NewGetPeerAddressesResponseMessage(addressMessages, bannedAddressMessages)
return response, nil
}

View File

@@ -1,13 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetSelectedTipHash handles the respectively named RPC command
func HandleGetSelectedTipHash(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
response := appmessage.NewGetSelectedTipHashResponseMessage(context.DAG.SelectedTipHash().String())
return response, nil
}

View File

@@ -1,34 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
// HandleGetSubnetwork handles the respectively named RPC command
func HandleGetSubnetwork(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getSubnetworkRequest := request.(*appmessage.GetSubnetworkRequestMessage)
subnetworkID, err := subnetworkid.NewFromStr(getSubnetworkRequest.SubnetworkID)
if err != nil {
errorMessage := &appmessage.GetSubnetworkResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse subnetworkID: %s", err)
return errorMessage, nil
}
var gasLimit uint64
if !subnetworkID.IsBuiltInOrNative() {
limit, err := context.DAG.GasLimit(subnetworkID)
if err != nil {
errorMessage := &appmessage.GetSubnetworkResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Subnetwork %s not found.", subnetworkID)
return errorMessage, nil
}
gasLimit = limit
}
response := appmessage.NewGetSubnetworkResponseMessage(gasLimit)
return response, nil
}

View File

@@ -1,9 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/panics"
)
var log, _ = logger.Get(logger.SubsystemTags.RPCS)
var spawn = panics.GoroutineWrapperFunc(log)

View File

@@ -1,19 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleNotifyBlockAdded handles the respectively named RPC command
func HandleNotifyBlockAdded(context *rpccontext.Context, router *router.Router, _ appmessage.Message) (appmessage.Message, error) {
listener, err := context.NotificationManager.Listener(router)
if err != nil {
return nil, err
}
listener.PropagateBlockAddedNotifications()
response := appmessage.NewNotifyBlockAddedResponseMessage()
return response, nil
}

View File

@@ -1,25 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleNotifyChainChanged handles the respectively named RPC command
func HandleNotifyChainChanged(context *rpccontext.Context, router *router.Router, _ appmessage.Message) (appmessage.Message, error) {
if context.AcceptanceIndex == nil {
errorMessage := appmessage.NewNotifyChainChangedResponseMessage()
errorMessage.Error = appmessage.RPCErrorf("Acceptance index is not available")
return errorMessage, nil
}
listener, err := context.NotificationManager.Listener(router)
if err != nil {
return nil, err
}
listener.PropagateChainChangedNotifications()
response := appmessage.NewNotifyChainChangedResponseMessage()
return response, nil
}

View File

@@ -1,20 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleNotifyFinalityConflicts handles the respectively named RPC command
func HandleNotifyFinalityConflicts(context *rpccontext.Context, router *router.Router, _ appmessage.Message) (appmessage.Message, error) {
listener, err := context.NotificationManager.Listener(router)
if err != nil {
return nil, err
}
listener.PropagateFinalityConflictNotifications()
listener.PropagateFinalityConflictResolvedNotifications()
response := appmessage.NewNotifyFinalityConflictsResponseMessage()
return response, nil
}

View File

@@ -1,30 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
)
// HandleResolveFinalityConflict handles the respectively named RPC command
func HandleResolveFinalityConflict(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
ResolveFinalityConflictRequest := request.(*appmessage.ResolveFinalityConflictRequestMessage)
finalityBlockHash, err := daghash.NewHashFromStr(ResolveFinalityConflictRequest.FinalityBlockHash)
if err != nil {
errorMessage := &appmessage.ResolveFinalityConflictResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse finalityBlockHash: %s", err)
return errorMessage, nil
}
err = context.DAG.ResolveFinalityConflict(finalityBlockHash)
if err != nil {
errorMessage := &appmessage.ResolveFinalityConflictResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not resolve finality conflict: %s", err)
return errorMessage, nil
}
response := appmessage.NewResolveFinalityConflictResponseMessage()
return response, nil
}

View File

@@ -1,25 +0,0 @@
package rpchandlers
import (
"time"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
const pauseBeforeShutDown = time.Second
// HandleShutDown handles the respectively named RPC command
func HandleShutDown(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
log.Warn("ShutDown RPC called.")
// Wait a second before shutting down, to allow time to return the response to the caller
spawn("HandleShutDown-pauseAndShutDown", func() {
<-time.After(pauseBeforeShutDown)
close(context.ShutDownChan)
})
response := appmessage.NewShutDownResponseMessage()
return response, nil
}

View File

@@ -1,41 +0,0 @@
package rpchandlers
import (
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
)
// HandleSubmitBlock handles the respectively named RPC command
func HandleSubmitBlock(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
submitBlockRequest := request.(*appmessage.SubmitBlockRequestMessage)
// Deserialize the submitted block.
serializedBlock, err := hex.DecodeString(submitBlockRequest.BlockHex)
if err != nil {
errorMessage := &appmessage.SubmitBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block hex could not be parsed: %s", err)
return errorMessage, nil
}
block, err := util.NewBlockFromBytes(serializedBlock)
if err != nil {
errorMessage := &appmessage.SubmitBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block decode failed: %s", err)
return errorMessage, nil
}
err = context.ProtocolManager.AddBlock(block, blockdag.BFDisallowDelay|blockdag.BFDisallowOrphans)
if err != nil {
errorMessage := &appmessage.SubmitBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block rejected. Reason: %s", err)
return errorMessage, nil
}
log.Infof("Accepted block %s via submitBlock", block.Hash())
response := appmessage.NewSubmitBlockResponseMessage()
return response, nil
}

View File

@@ -1,47 +0,0 @@
package rpchandlers
import (
"bytes"
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/mempool"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
// HandleSubmitTransaction handles the respectively named RPC command
func HandleSubmitTransaction(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
submitTransactionRequest := request.(*appmessage.SubmitTransactionRequestMessage)
serializedTx, err := hex.DecodeString(submitTransactionRequest.TransactionHex)
if err != nil {
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Transaction hex could not be parsed: %s", err)
return errorMessage, nil
}
var msgTx appmessage.MsgTx
err = msgTx.Deserialize(bytes.NewReader(serializedTx))
if err != nil {
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Transaction decode failed: %s", err)
return errorMessage, nil
}
tx := util.NewTx(&msgTx)
err = context.ProtocolManager.AddTransaction(tx)
if err != nil {
if !errors.As(err, &mempool.RuleError{}) {
return nil, err
}
log.Debugf("Rejected transaction %s: %s", tx.ID(), err)
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Rejected transaction %s: %s", tx.ID(), err)
return errorMessage, nil
}
response := appmessage.NewSubmitTransactionResponseMessage(tx.ID().String())
return response, nil
}

154
blockdag/accept.go Normal file
View File

@@ -0,0 +1,154 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
func (dag *BlockDAG) addNodeToIndexWithInvalidAncestor(block *util.Block) error {
blockHeader := &block.MsgBlock().Header
newNode, _ := dag.newBlockNode(blockHeader, newBlockSet())
newNode.status = statusInvalidAncestor
dag.index.AddNode(newNode)
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
err = dag.index.flushToDB(dbTx)
if err != nil {
return err
}
return dbTx.Commit()
}
// maybeAcceptBlock potentially accepts a block into the block DAG. It
// performs several validation checks which depend on its position within
// the block DAG before adding it. The block is expected to have already
// gone through ProcessBlock before calling this function with it.
//
// The flags are also passed to checkBlockContext and connectToDAG. See
// their documentation for how the flags modify their behavior.
//
// This function MUST be called with the dagLock held (for writes).
func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) error {
parents, err := lookupParentNodes(block, dag)
if err != nil {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrInvalidAncestorBlock {
err := dag.addNodeToIndexWithInvalidAncestor(block)
if err != nil {
return err
}
}
return err
}
// The block must pass all of the validation rules which depend on the
// position of the block within the block DAG.
err = dag.checkBlockContext(block, parents, flags)
if err != nil {
return err
}
// Create a new block node for the block and add it to the node index.
newNode, selectedParentAnticone := dag.newBlockNode(&block.MsgBlock().Header, parents)
newNode.status = statusDataStored
dag.index.AddNode(newNode)
// Insert the block into the database if it's not already there. Even
// though it is possible the block will ultimately fail to connect, it
// has already passed all proof-of-work and validity tests which means
// it would be prohibitively expensive for an attacker to fill up the
// disk with a bunch of blocks that fail to connect. This is necessary
// since it allows block download to be decoupled from the much more
// expensive connection logic. It also has some other nice properties
// such as making blocks that never become part of the DAG or
// blocks that fail to connect available for further analysis.
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
blockExists, err := dbaccess.HasBlock(dbTx, block.Hash())
if err != nil {
return err
}
if !blockExists {
err := storeBlock(dbTx, block)
if err != nil {
return err
}
}
err = dag.index.flushToDB(dbTx)
if err != nil {
return err
}
err = dbTx.Commit()
if err != nil {
return err
}
// Make sure that all the block's transactions are finalized
fastAdd := flags&BFFastAdd == BFFastAdd
bluestParent := parents.bluest()
if !fastAdd {
if err := dag.validateAllTxsFinalized(block, newNode, bluestParent); err != nil {
return err
}
}
// Connect the passed block to the DAG. This also handles validation of the
// transaction scripts.
chainUpdates, err := dag.addBlock(newNode, block, selectedParentAnticone, flags)
if err != nil {
return err
}
// Notify the caller that the new block was accepted into the block
// DAG. The caller would typically want to react by relaying the
// inventory to other peers.
dag.dagLock.Unlock()
dag.sendNotification(NTBlockAdded, &BlockAddedNotificationData{
Block: block,
WasUnorphaned: flags&BFWasUnorphaned != 0,
})
if len(chainUpdates.addedChainBlockHashes) > 0 {
dag.sendNotification(NTChainChanged, &ChainChangedNotificationData{
RemovedChainBlockHashes: chainUpdates.removedChainBlockHashes,
AddedChainBlockHashes: chainUpdates.addedChainBlockHashes,
})
}
dag.dagLock.Lock()
return nil
}
func lookupParentNodes(block *util.Block, dag *BlockDAG) (blockSet, error) {
header := block.MsgBlock().Header
parentHashes := header.ParentHashes
nodes := newBlockSet()
for _, parentHash := range parentHashes {
node, ok := dag.index.LookupNode(parentHash)
if !ok {
str := fmt.Sprintf("parent block %s is unknown", parentHash)
return nil, ruleError(ErrParentBlockUnknown, str)
} else if dag.index.NodeStatus(node).KnownInvalid() {
str := fmt.Sprintf("parent block %s is known to be invalid", parentHash)
return nil, ruleError(ErrInvalidAncestorBlock, str)
}
nodes.add(node)
}
return nodes, nil
}

107
blockdag/accept_test.go Normal file
View File

@@ -0,0 +1,107 @@
package blockdag
import (
"errors"
"path/filepath"
"testing"
"github.com/kaspanet/kaspad/dagconfig"
)
func TestMaybeAcceptBlockErrors(t *testing.T) {
// Create a new database and DAG instance to run tests against.
dag, teardownFunc, err := DAGSetup("TestMaybeAcceptBlockErrors", true, Config{
DAGParams: &dagconfig.SimnetParams,
})
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
dag.TestSetCoinbaseMaturity(0)
// Test rejecting the block if its parents are missing
orphanBlockFile := "blk_3B.dat"
loadedBlocks, err := LoadBlocks(filepath.Join("testdata/", orphanBlockFile))
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: "+
"Error loading file '%s': %s\n", orphanBlockFile, err)
}
block := loadedBlocks[0]
err = dag.maybeAcceptBlock(block, BFNone)
if err == nil {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
"Expected: %s, got: <nil>", ErrParentBlockUnknown)
}
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); !ok {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
"Expected RuleError but got %s", err)
} else if ruleErr.ErrorCode != ErrParentBlockUnknown {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
"Unexpected error code. Want: %s, got: %s", ErrParentBlockUnknown, ruleErr.ErrorCode)
}
// Test rejecting the block if its parents are invalid
blocksFile := "blk_0_to_4.dat"
blocks, err := LoadBlocks(filepath.Join("testdata/", blocksFile))
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: "+
"Error loading file '%s': %s\n", blocksFile, err)
}
// Add a valid block and mark it as invalid
block1 := blocks[1]
isOrphan, isDelayed, err := dag.ProcessBlock(block1, BFNone)
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: Valid block unexpectedly returned an error: %s", err)
}
if isDelayed {
t.Fatalf("TestMaybeAcceptBlockErrors: block 1 is too far in the future")
}
if isOrphan {
t.Fatalf("TestMaybeAcceptBlockErrors: incorrectly returned block 1 is an orphan")
}
blockNode1, ok := dag.index.LookupNode(block1.Hash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", block1.Hash())
}
dag.index.SetStatusFlags(blockNode1, statusValidateFailed)
block2 := blocks[2]
err = dag.maybeAcceptBlock(block2, BFNone)
if err == nil {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
"Expected: %s, got: <nil>", ErrInvalidAncestorBlock)
}
if ok := errors.As(err, &ruleErr); !ok {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
"Expected RuleError but got %s", err)
} else if ruleErr.ErrorCode != ErrInvalidAncestorBlock {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
"Unexpected error. Want: %s, got: %s", ErrInvalidAncestorBlock, ruleErr.ErrorCode)
}
// Set block1's status back to valid for next tests
dag.index.UnsetStatusFlags(blockNode1, statusValidateFailed)
// Test rejecting the block due to bad context
originalBits := block2.MsgBlock().Header.Bits
block2.MsgBlock().Header.Bits = 0
err = dag.maybeAcceptBlock(block2, BFNone)
if err == nil {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
"Expected: %s, got: <nil>", ErrUnexpectedDifficulty)
}
if ok := errors.As(err, &ruleErr); !ok {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
"Expected RuleError but got %s", err)
} else if ruleErr.ErrorCode != ErrUnexpectedDifficulty {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
"Unexpected error. Want: %s, got: %s", ErrUnexpectedDifficulty, ruleErr.ErrorCode)
}
// Set block2's bits back to valid for next tests
block2.MsgBlock().Header.Bits = originalBits
}

View File

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

View File

@@ -5,14 +5,10 @@
package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/dbaccess"
"sync"
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util/daghash"
)
@@ -61,18 +57,6 @@ func (bi *blockIndex) LookupNode(hash *daghash.Hash) (*blockNode, bool) {
return node, ok
}
func (bi *blockIndex) LookupNodes(hashes []*daghash.Hash) ([]*blockNode, error) {
blocks := make([]*blockNode, 0, len(hashes))
for _, hash := range hashes {
node, ok := bi.LookupNode(hash)
if !ok {
return nil, errors.Errorf("Couldn't find block with hash %s", hash)
}
blocks = append(blocks, node)
}
return blocks, nil
}
// AddNode adds the provided node to the block index and marks it as dirty.
// Duplicate entries are not checked so it is up to caller to avoid adding them.
//
@@ -92,23 +76,36 @@ func (bi *blockIndex) addNode(node *blockNode) {
bi.index[*node.hash] = node
}
// BlockNodeStatus provides concurrent-safe access to the status field of a node.
// NodeStatus provides concurrent-safe access to the status field of a node.
//
// This function is safe for concurrent access.
func (bi *blockIndex) BlockNodeStatus(node *blockNode) blockStatus {
func (bi *blockIndex) NodeStatus(node *blockNode) blockStatus {
bi.RLock()
defer bi.RUnlock()
status := node.status
return status
}
// SetBlockNodeStatus changes the status of a blockNode
// SetStatusFlags flips the provided status flags on the block node to on,
// regardless of whether they were on or off previously. This does not unset any
// flags currently on.
//
// This function is safe for concurrent access.
func (bi *blockIndex) SetBlockNodeStatus(node *blockNode, newStatus blockStatus) {
func (bi *blockIndex) SetStatusFlags(node *blockNode, flags blockStatus) {
bi.Lock()
defer bi.Unlock()
node.status = newStatus
node.status |= flags
bi.dirty[node] = struct{}{}
}
// UnsetStatusFlags flips the provided status flags on the block node to off,
// regardless of whether they were on or off previously.
//
// 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{}{}
}
@@ -137,42 +134,3 @@ func (bi *blockIndex) flushToDB(dbContext *dbaccess.TxContext) error {
func (bi *blockIndex) clearDirtyEntries() {
bi.dirty = make(map[*blockNode]struct{})
}
func (dag *BlockDAG) addNodeToIndexWithInvalidAncestor(block *util.Block) error {
blockHeader := &block.MsgBlock().Header
newNode, _ := dag.newBlockNode(blockHeader, newBlockSet())
newNode.status = statusInvalidAncestor
dag.index.AddNode(newNode)
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
err = dag.index.flushToDB(dbTx)
if err != nil {
return err
}
return dbTx.Commit()
}
func lookupParentNodes(block *util.Block, dag *BlockDAG) (blockSet, error) {
header := block.MsgBlock().Header
parentHashes := header.ParentHashes
nodes := newBlockSet()
for _, parentHash := range parentHashes {
node, ok := dag.index.LookupNode(parentHash)
if !ok {
str := fmt.Sprintf("parent block %s is unknown", parentHash)
return nil, ruleError(ErrParentBlockUnknown, str)
} else if dag.index.BlockNodeStatus(node).KnownInvalid() {
str := fmt.Sprintf("parent block %s is known to be invalid", parentHash)
return nil, ruleError(ErrInvalidAncestorBlock, str)
}
nodes.add(node)
}
return nodes, nil
}

View File

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

109
blockdag/blocklocator.go Normal file
View File

@@ -0,0 +1,109 @@
package blockdag
import (
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
// BlockLocator is used to help locate a specific block. The algorithm for
// building the block locator is to add block hashes in reverse order on the
// block's selected parent chain until the desired stop block is reached.
// In order to keep the list of locator hashes to a reasonable number of entries,
// the step between each entry is doubled each loop iteration to exponentially
// decrease the number of hashes as a function of the distance from the block
// being located.
//
// For example, assume a selected parent chain with IDs as depicted below, and the
// stop block is genesis:
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
//
// The block locator for block 17 would be the hashes of blocks:
// [17 16 14 11 7 2 genesis]
type BlockLocator []*daghash.Hash
// BlockLocatorFromHashes returns a block locator from high and low hash.
// See BlockLocator for details on the algorithm used to create a block locator.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) BlockLocatorFromHashes(highHash, lowHash *daghash.Hash) (BlockLocator, error) {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
highNode, ok := dag.index.LookupNode(highHash)
if !ok {
return nil, errors.Errorf("block %s is unknown", highHash)
}
lowNode, ok := dag.index.LookupNode(lowHash)
if !ok {
return nil, errors.Errorf("block %s is unknown", lowHash)
}
return dag.blockLocator(highNode, lowNode)
}
// blockLocator returns a block locator for the passed high and low nodes.
// See the BlockLocator type comments for more details.
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) blockLocator(highNode, lowNode *blockNode) (BlockLocator, error) {
// We use the selected parent of the high node, so the
// block locator won't contain the high node.
highNode = highNode.selectedParent
node := highNode
step := uint64(1)
locator := make(BlockLocator, 0)
for node != nil {
locator = append(locator, node.hash)
// Nothing more to add once the low node has been added.
if node.blueScore <= lowNode.blueScore {
if node != lowNode {
return nil, errors.Errorf("highNode and lowNode are " +
"not in the same selected parent chain.")
}
break
}
// Calculate blueScore of previous node to include ensuring the
// final node is lowNode.
nextBlueScore := node.blueScore - step
if nextBlueScore < lowNode.blueScore {
nextBlueScore = lowNode.blueScore
}
// walk backwards through the nodes to the correct ancestor.
node = node.SelectedAncestor(nextBlueScore)
// Double the distance between included hashes.
step *= 2
}
return locator, nil
}
// FindNextLocatorBoundaries returns the lowest unknown block locator, hash
// and the highest known block locator hash. This is used to create the
// next block locator to find the highest shared known chain block with the
// sync peer.
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) FindNextLocatorBoundaries(locator BlockLocator) (highHash, lowHash *daghash.Hash) {
// Find the most recent locator block hash in the DAG. In the case none of
// the hashes in the locator are in the DAG, fall back to the genesis block.
lowNode := dag.genesis
nextBlockLocatorIndex := int64(len(locator) - 1)
for i, hash := range locator {
node, ok := dag.index.LookupNode(hash)
if ok {
lowNode = node
nextBlockLocatorIndex = int64(i) - 1
break
}
}
if nextBlockLocatorIndex < 0 {
return nil, lowNode.hash
}
return locator[nextBlockLocatorIndex], lowNode.hash
}

View File

@@ -8,20 +8,20 @@ import (
"fmt"
"math"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/util/daghash"
)
// blockStatus is representing the validation state of the block.
// blockStatus is a bit field representing the validation state of the block.
type blockStatus byte
const (
// statusDataStored indicates that the block's payload is stored on disk.
statusDataStored blockStatus = iota
statusDataStored blockStatus = 1 << iota
// statusValid indicates that the block has been fully validated.
statusValid
@@ -32,33 +32,12 @@ const (
// statusInvalidAncestor indicates that one of the block's ancestors has
// has failed validation, thus the block is also invalid.
statusInvalidAncestor
// statusUTXOPendingVerification indicates that the block is pending verification against its past UTXO-Set, either
// because it was not yet verified since the block was never in the selected parent chain, or if the
// block violates finality.
statusUTXOPendingVerification
// statusDisqualifiedFromChain indicates that the block is not eligible to be a selected parent.
statusDisqualifiedFromChain
)
var blockStatusToString = map[blockStatus]string{
statusDataStored: "statusDataStored",
statusValid: "statusValid",
statusValidateFailed: "statusValidateFailed",
statusInvalidAncestor: "statusInvalidAncestor",
statusUTXOPendingVerification: "statusUTXOPendingVerification",
statusDisqualifiedFromChain: "statusDisqualifiedFromChain",
}
func (status blockStatus) String() string {
return blockStatusToString[status]
}
// KnownValid returns whether the block is known to be valid. This will return
// false for a valid block that has not been fully validated yet.
func (status blockStatus) KnownValid() bool {
return status == statusValid
return status&statusValid != 0
}
// KnownInvalid returns whether the block is known to be invalid. This may be
@@ -66,7 +45,7 @@ func (status blockStatus) KnownValid() bool {
// invalid. This will return false for invalid blocks that have not been proven
// invalid yet.
func (status blockStatus) KnownInvalid() bool {
return status == statusValidateFailed || status == statusInvalidAncestor
return status&(statusValidateFailed|statusInvalidAncestor) != 0
}
// blockNode represents a block within the block DAG. The DAG is stored into
@@ -79,9 +58,6 @@ type blockNode struct {
// hundreds of thousands of these in memory, so a few extra bytes of
// padding adds up.
// dag is the blockDAG in which this node resides
dag *BlockDAG
// parents is the parent blocks for this node.
parents blockSet
@@ -92,12 +68,9 @@ type blockNode struct {
// children are all the blocks that refer to this block as a parent
children blockSet
// blues are all blue blocks in this block's worldview that are in its merge set
// blues are all blue blocks in this block's worldview that are in its selected parent anticone
blues []*blockNode
// reds are all red blocks in this block's worldview that are in its merge set
reds []*blockNode
// blueScore is the count of all the blue blocks in this block's past
blueScore uint64
@@ -108,7 +81,7 @@ type blockNode struct {
// hash is the double sha 256 of the block.
hash *daghash.Hash
// Some fields from block headers to aid in reconstructing headers
// Some fields from block headers to aid in reconstructing headers
// from memory. These must be treated as immutable and are intentionally
// ordered to avoid padding on 64-bit platforms.
version int32
@@ -121,18 +94,20 @@ type blockNode struct {
// status is a bitfield representing the validation state of the block. The
// status field, unlike the other fields, may be written to and so should
// only be accessed using the concurrent-safe BlockNodeStatus method on
// only be accessed using the concurrent-safe NodeStatus method on
// blockIndex once the node has been added to the global index.
status blockStatus
// isFinalized determines whether the node is below the finality point.
isFinalized bool
}
// newBlockNode returns a new block node for the given block header and parents, and the
// anticone of its selected parent (parent with highest blue score).
// selectedParentAnticone is used to update reachability data we store for future reachability queries.
// This function is NOT safe for concurrent access.
func (dag *BlockDAG) newBlockNode(blockHeader *appmessage.BlockHeader, parents blockSet) (node *blockNode, selectedParentAnticone []*blockNode) {
func (dag *BlockDAG) newBlockNode(blockHeader *domainmessage.BlockHeader, parents blockSet) (node *blockNode, selectedParentAnticone []*blockNode) {
node = &blockNode{
dag: dag,
parents: parents,
children: make(blockSet),
blueScore: math.MaxUint64, // Initialized to the max value to avoid collisions with the genesis block
@@ -185,9 +160,9 @@ func (node *blockNode) less(other *blockNode) bool {
// Header constructs a block header from the node and returns it.
//
// This function is safe for concurrent access.
func (node *blockNode) Header() *appmessage.BlockHeader {
func (node *blockNode) Header() *domainmessage.BlockHeader {
// No lock is needed because all accessed fields are immutable.
return &appmessage.BlockHeader{
return &domainmessage.BlockHeader{
Version: node.version,
ParentHashes: node.ParentHashes(),
HashMerkleRoot: node.hashMerkleRoot,
@@ -230,8 +205,8 @@ func (node *blockNode) RelativeAncestor(distance uint64) *blockNode {
// prior to, and including, the block node.
//
// This function is safe for concurrent access.
func (node *blockNode) PastMedianTime() mstime.Time {
window := blueBlockWindow(node, 2*node.dag.TimestampDeviationTolerance-1)
func (node *blockNode) PastMedianTime(dag *BlockDAG) mstime.Time {
window := blueBlockWindow(node, 2*dag.TimestampDeviationTolerance-1)
medianTimestamp, err := window.medianTimestamp()
if err != nil {
panic(fmt.Sprintf("blueBlockWindow: %s", err))
@@ -239,14 +214,6 @@ func (node *blockNode) PastMedianTime() mstime.Time {
return mstime.UnixMilliseconds(medianTimestamp)
}
func (node *blockNode) selectedParentMedianTime() mstime.Time {
medianTime := node.Header().Timestamp
if !node.isGenesis() {
medianTime = node.selectedParent.PastMedianTime()
}
return medianTime
}
func (node *blockNode) ParentHashes() []*daghash.Hash {
return node.parents.hashes()
}
@@ -256,8 +223,8 @@ func (node *blockNode) isGenesis() bool {
return len(node.parents) == 0
}
func (node *blockNode) finalityScore() uint64 {
return node.blueScore / node.dag.FinalityInterval()
func (node *blockNode) finalityScore(dag *BlockDAG) uint64 {
return node.blueScore / uint64(dag.FinalityInterval())
}
// String returns a string that contains the block hash.
@@ -268,111 +235,3 @@ func (node blockNode) String() string {
func (node *blockNode) time() mstime.Time {
return mstime.UnixMilliseconds(node.timestamp)
}
func (node *blockNode) blockAtDepth(depth uint64) *blockNode {
if node.blueScore <= depth { // to prevent overflow of requiredBlueScore
depth = node.blueScore
}
current := node
requiredBlueScore := node.blueScore - depth
for current.blueScore >= requiredBlueScore {
if current.isGenesis() {
return current
}
current = current.selectedParent
}
return current
}
func (node *blockNode) finalityPoint() *blockNode {
return node.blockAtDepth(node.dag.FinalityInterval())
}
func (node *blockNode) hasFinalityPointInOthersSelectedChain(other *blockNode) (bool, error) {
finalityPoint := node.finalityPoint()
return node.dag.isInSelectedParentChainOf(finalityPoint, other)
}
func (node *blockNode) nonBoundedMergeDepthViolatingBlues() (blockSet, error) {
nonBoundedMergeDepthViolatingBlues := newBlockSet()
for _, blueNode := range node.blues {
notViolatingFinality, err := node.hasFinalityPointInOthersSelectedChain(blueNode)
if err != nil {
return nil, err
}
if notViolatingFinality {
nonBoundedMergeDepthViolatingBlues.add(blueNode)
}
}
return nonBoundedMergeDepthViolatingBlues, nil
}
func (node *blockNode) checkBoundedMergeDepth() error {
nonBoundedMergeDepthViolatingBlues, err := node.nonBoundedMergeDepthViolatingBlues()
if err != nil {
return err
}
finalityPoint := node.finalityPoint()
for _, red := range node.reds {
doesRedHaveFinalityPointInPast, err := node.dag.isInPast(finalityPoint, red)
if err != nil {
return err
}
isRedInPastOfAnyNonFinalityViolatingBlue, err := node.dag.isInPastOfAny(red, nonBoundedMergeDepthViolatingBlues)
if err != nil {
return err
}
if !doesRedHaveFinalityPointInPast && !isRedInPastOfAnyNonFinalityViolatingBlue {
return ruleError(ErrViolatingBoundedMergeDepth, "block is violating bounded merge depth")
}
}
return nil
}
func (node *blockNode) isViolatingFinality() (bool, error) {
if node.isGenesis() {
return false, nil
}
if node.dag.virtual.less(node) {
isVirtualFinalityPointInNodesSelectedChain, err := node.dag.isInSelectedParentChainOf(
node.dag.virtual.finalityPoint(), node.selectedParent) // use node.selectedParent because node still doesn't have reachability data
if err != nil {
return false, err
}
if !isVirtualFinalityPointInNodesSelectedChain {
return true, nil
}
}
return false, nil
}
func (node *blockNode) checkMergeSizeLimit() error {
mergeSetSize := len(node.reds) + len(node.blues)
if mergeSetSize > mergeSetSizeLimit {
return ruleError(ErrViolatingMergeLimit,
fmt.Sprintf("The block merges %d blocks > %d merge set size limit", mergeSetSize, mergeSetSizeLimit))
}
return nil
}
func (node *blockNode) hasValidChildren() bool {
for child := range node.children {
if node.dag.index.BlockNodeStatus(child) == statusValid {
return true
}
}
return false
}

View File

@@ -1,10 +1,9 @@
package blockdag
import (
"testing"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util/daghash"
"testing"
)
// This test is to ensure the size BlueAnticoneSizesSize is serialized to the size of KType.

View File

@@ -36,7 +36,7 @@ func (bs blockSet) remove(node *blockNode) {
// clone clones thie block set
func (bs blockSet) clone() blockSet {
clone := make(blockSet, len(bs))
clone := newBlockSet()
for node := range bs {
clone.add(node)
}
@@ -104,48 +104,14 @@ func (bs blockSet) String() string {
func (bs blockSet) bluest() *blockNode {
var bluestNode *blockNode
var maxScore uint64
for node := range bs {
if bluestNode == nil || bluestNode.less(node) {
if bluestNode == nil ||
node.blueScore > maxScore ||
(node.blueScore == maxScore && daghash.Less(node.hash, bluestNode.hash)) {
bluestNode = node
maxScore = node.blueScore
}
}
return bluestNode
}
func (bs blockSet) isEqual(other blockSet) bool {
if len(bs) != len(other) {
return false
}
for node := range bs {
if !other.contains(node) {
return false
}
}
return true
}
func (bs blockSet) areAllIn(other blockSet) bool {
for node := range bs {
if !other.contains(node) {
return false
}
}
return true
}
// isOnlyGenesis returns true if the only block in this blockSet is the genesis block
func (bs blockSet) isOnlyGenesis() bool {
if len(bs) != 1 {
return false
}
for node := range bs {
if node.isGenesis() {
return true
}
}
return false
}

View File

@@ -243,68 +243,3 @@ func TestBlockSetUnion(t *testing.T) {
}
}
}
func TestBlockSetAreAllIn(t *testing.T) {
node1 := &blockNode{hash: &daghash.Hash{10}}
node2 := &blockNode{hash: &daghash.Hash{20}}
node3 := &blockNode{hash: &daghash.Hash{30}}
tests := []struct {
name string
set blockSet
other blockSet
expectedResult bool
}{
{
name: "two empty sets",
set: blockSetFromSlice(),
other: blockSetFromSlice(),
expectedResult: true,
},
{
name: "set empty, other full",
set: blockSetFromSlice(),
other: blockSetFromSlice(node1, node2, node3),
expectedResult: true,
},
{
name: "set full, other empty",
set: blockSetFromSlice(node1, node2, node3),
other: blockSetFromSlice(),
expectedResult: false,
},
{
name: "same node in both",
set: blockSetFromSlice(node1),
other: blockSetFromSlice(node1),
expectedResult: true,
},
{
name: "different node in both",
set: blockSetFromSlice(node1),
other: blockSetFromSlice(node2),
expectedResult: false,
},
{
name: "set is subset of other",
set: blockSetFromSlice(node1, node2),
other: blockSetFromSlice(node2, node1, node3),
expectedResult: true,
},
{
name: "other is subset of set",
set: blockSetFromSlice(node2, node1, node3),
other: blockSetFromSlice(node1, node2),
expectedResult: false,
},
}
for _, test := range tests {
result := test.set.areAllIn(test.other)
if result != test.expectedResult {
t.Errorf("blockSet.areAllIn: unexpected result in test '%s'. "+
"Expected: '%t', got: '%t'", test.name, test.expectedResult, result)
}
}
}

View File

@@ -1,13 +1,12 @@
package blockdag
import (
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"reflect"
"testing"
"time"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
func TestBlueBlockWindow(t *testing.T) {
@@ -52,12 +51,12 @@ func TestBlueBlockWindow(t *testing.T) {
expectedWindowWithGenesisPadding: []string{"B", "A", "A", "A", "A", "A", "A", "A", "A", "A"},
},
{
parents: []string{"C", "D"},
parents: []string{"D", "C"},
id: "E",
expectedWindowWithGenesisPadding: []string{"D", "C", "B", "A", "A", "A", "A", "A", "A", "A"},
},
{
parents: []string{"C", "D"},
parents: []string{"D", "C"},
id: "F",
expectedWindowWithGenesisPadding: []string{"D", "C", "B", "A", "A", "A", "A", "A", "A", "A"},
},
@@ -118,13 +117,13 @@ func TestBlueBlockWindow(t *testing.T) {
block, err := PrepareBlockForTest(dag, parents.hashes(), nil)
if err != nil {
t.Fatalf("block %v got unexpected error from PrepareBlockForTest: %+v", blockData.id, err)
t.Fatalf("block %v got unexpected error from PrepareBlockForTest: %v", blockData.id, err)
}
utilBlock := util.NewBlock(block)
isOrphan, isDelayed, err := dag.ProcessBlock(utilBlock, BFNoPoWCheck)
if err != nil {
t.Fatalf("dag.ProcessBlock got unexpected error for block %v: %+v", blockData.id, err)
t.Fatalf("dag.ProcessBlock got unexpected error for block %v: %v", blockData.id, err)
}
if isDelayed {
t.Fatalf("block %s "+

199
blockdag/coinbase.go Normal file
View File

@@ -0,0 +1,199 @@
package blockdag
import (
"bufio"
"bytes"
"encoding/binary"
"io"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/coinbasepayload"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/util/txsort"
"github.com/pkg/errors"
)
// compactFeeData is a specialized data type to store a compact list of fees
// inside a block.
// Every transaction gets a single uint64 value, stored as a plain binary list.
// The transactions are ordered the same way they are ordered inside the block, making it easy
// to traverse every transaction in a block and extract its fee.
//
// compactFeeFactory is used to create such a list.
// compactFeeIterator is used to iterate over such a list.
type compactFeeData []byte
func (cfd compactFeeData) Len() int {
return len(cfd) / 8
}
type compactFeeFactory struct {
buffer *bytes.Buffer
writer *bufio.Writer
}
func newCompactFeeFactory() *compactFeeFactory {
buffer := bytes.NewBuffer([]byte{})
return &compactFeeFactory{
buffer: buffer,
writer: bufio.NewWriter(buffer),
}
}
func (cfw *compactFeeFactory) add(txFee uint64) error {
return binary.Write(cfw.writer, binary.LittleEndian, txFee)
}
func (cfw *compactFeeFactory) data() (compactFeeData, error) {
err := cfw.writer.Flush()
return compactFeeData(cfw.buffer.Bytes()), err
}
type compactFeeIterator struct {
reader io.Reader
}
func (cfd compactFeeData) iterator() *compactFeeIterator {
return &compactFeeIterator{
reader: bufio.NewReader(bytes.NewBuffer(cfd)),
}
}
func (cfr *compactFeeIterator) next() (uint64, error) {
var txFee uint64
err := binary.Read(cfr.reader, binary.LittleEndian, &txFee)
return txFee, err
}
// The following functions relate to storing and retrieving fee data from the database
// getBluesFeeData returns the compactFeeData for all nodes's blues,
// used to calculate the fees this blockNode needs to pay
func (dag *BlockDAG) getBluesFeeData(node *blockNode) (map[daghash.Hash]compactFeeData, error) {
bluesFeeData := make(map[daghash.Hash]compactFeeData)
for _, blueBlock := range node.blues {
feeData, err := dbaccess.FetchFeeData(dag.databaseContext, blueBlock.hash)
if err != nil {
return nil, err
}
bluesFeeData[*blueBlock.hash] = feeData
}
return bluesFeeData, nil
}
// The following functions deal with building and validating the coinbase transaction
func (node *blockNode) validateCoinbaseTransaction(dag *BlockDAG, block *util.Block, txsAcceptanceData MultiBlockTxsAcceptanceData) error {
if node.isGenesis() {
return nil
}
blockCoinbaseTx := block.CoinbaseTransaction().MsgTx()
_, scriptPubKey, extraData, err := coinbasepayload.DeserializeCoinbasePayload(blockCoinbaseTx)
if errors.Is(err, coinbasepayload.ErrIncorrectScriptPubKeyLen) {
return ruleError(ErrBadCoinbaseTransaction, err.Error())
}
if err != nil {
return err
}
expectedCoinbaseTransaction, err := node.expectedCoinbaseTransaction(dag, txsAcceptanceData, scriptPubKey, extraData)
if err != nil {
return err
}
if !expectedCoinbaseTransaction.Hash().IsEqual(block.CoinbaseTransaction().Hash()) {
return ruleError(ErrBadCoinbaseTransaction, "Coinbase transaction is not built as expected")
}
return nil
}
// expectedCoinbaseTransaction returns the coinbase transaction for the current block
func (node *blockNode) expectedCoinbaseTransaction(dag *BlockDAG, txsAcceptanceData MultiBlockTxsAcceptanceData, scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
bluesFeeData, err := dag.getBluesFeeData(node)
if err != nil {
return nil, err
}
txIns := []*domainmessage.TxIn{}
txOuts := []*domainmessage.TxOut{}
for _, blue := range node.blues {
txOut, err := coinbaseOutputForBlueBlock(dag, blue, txsAcceptanceData, bluesFeeData)
if err != nil {
return nil, err
}
if txOut != nil {
txOuts = append(txOuts, txOut)
}
}
payload, err := coinbasepayload.SerializeCoinbasePayload(node.blueScore, scriptPubKey, extraData)
if err != nil {
return nil, err
}
coinbaseTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, txIns, txOuts, subnetworkid.SubnetworkIDCoinbase, 0, payload)
sortedCoinbaseTx := txsort.Sort(coinbaseTx)
return util.NewTx(sortedCoinbaseTx), nil
}
// coinbaseOutputForBlueBlock calculates the output that should go into the coinbase transaction of blueBlock
// If blueBlock gets no fee - returns nil for txOut
func coinbaseOutputForBlueBlock(dag *BlockDAG, blueBlock *blockNode,
txsAcceptanceData MultiBlockTxsAcceptanceData, feeData map[daghash.Hash]compactFeeData) (*domainmessage.TxOut, error) {
blockTxsAcceptanceData, ok := txsAcceptanceData.FindAcceptanceData(blueBlock.hash)
if !ok {
return nil, errors.Errorf("No txsAcceptanceData for block %s", blueBlock.hash)
}
blockFeeData, ok := feeData[*blueBlock.hash]
if !ok {
return nil, errors.Errorf("No feeData for block %s", blueBlock.hash)
}
if len(blockTxsAcceptanceData.TxAcceptanceData) != blockFeeData.Len() {
return nil, errors.Errorf(
"length of accepted transaction data(%d) and fee data(%d) is not equal for block %s",
len(blockTxsAcceptanceData.TxAcceptanceData), blockFeeData.Len(), blueBlock.hash)
}
totalFees := uint64(0)
feeIterator := blockFeeData.iterator()
for _, txAcceptanceData := range blockTxsAcceptanceData.TxAcceptanceData {
fee, err := feeIterator.next()
if err != nil {
return nil, errors.Errorf("Error retrieving fee from compactFeeData iterator: %s", err)
}
if txAcceptanceData.IsAccepted {
totalFees += fee
}
}
totalReward := CalcBlockSubsidy(blueBlock.blueScore, dag.Params) + totalFees
if totalReward == 0 {
return nil, nil
}
// the ScriptPubKey for the coinbase is parsed from the coinbase payload
_, scriptPubKey, _, err := coinbasepayload.DeserializeCoinbasePayload(blockTxsAcceptanceData.TxAcceptanceData[0].Tx.MsgTx())
if err != nil {
return nil, err
}
txOut := &domainmessage.TxOut{
Value: totalReward,
ScriptPubKey: scriptPubKey,
}
return txOut, nil
}

60
blockdag/coinbase_test.go Normal file
View File

@@ -0,0 +1,60 @@
package blockdag
import (
"io"
"reflect"
"testing"
)
func TestFeeAccumulators(t *testing.T) {
fees := []uint64{1, 2, 3, 4, 5, 6, 7, 0xffffffffffffffff}
factory := newCompactFeeFactory()
for _, fee := range fees {
err := factory.add(fee)
if err != nil {
t.Fatalf("Error writing %d as tx fee: %s", fee, err)
}
}
expectedData := compactFeeData{
1, 0, 0, 0, 0, 0, 0, 0,
2, 0, 0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 0, 0, 0, 0,
4, 0, 0, 0, 0, 0, 0, 0,
5, 0, 0, 0, 0, 0, 0, 0,
6, 0, 0, 0, 0, 0, 0, 0,
7, 0, 0, 0, 0, 0, 0, 0,
255, 255, 255, 255, 255, 255, 255, 255,
}
actualData, err := factory.data()
if err != nil {
t.Fatalf("Error getting bytes from writer: %s", err)
}
if !reflect.DeepEqual(expectedData, actualData) {
t.Errorf("Expected bytes: %v, but got: %v", expectedData, actualData)
}
iterator := actualData.iterator()
for i, expectedFee := range fees {
actualFee, err := iterator.next()
if err != nil {
t.Fatalf("Error getting fee for Tx#%d: %s", i, err)
}
if actualFee != expectedFee {
t.Errorf("Tx #%d: Expected fee: %d, but got %d", i, expectedFee, actualFee)
}
}
_, err = iterator.next()
if err == nil {
t.Fatal("No error from iterator.nextTxFee after done reading all transactions")
}
if err != io.EOF {
t.Fatalf("Error from iterator.nextTxFee after done reading all transactions is not io.EOF: %s", err)
}
}

View File

@@ -17,8 +17,8 @@ import (
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
)
@@ -78,7 +78,7 @@ func loadUTXOSet(filename string) (UTXOSet, error) {
if err != nil {
return nil, err
}
utxoSet.utxoCache[appmessage.Outpoint{TxID: txID, Index: index}] = entry
utxoSet.utxoCollection[domainmessage.Outpoint{TxID: txID, Index: index}] = entry
}
return utxoSet, nil
@@ -103,6 +103,8 @@ func newTestDAG(params *dagconfig.Params) *BlockDAG {
TimestampDeviationTolerance: params.TimestampDeviationTolerance,
powMaxBits: util.BigToCompact(params.PowMax),
index: index,
warningCaches: newThresholdCaches(vbNumBits),
deploymentCaches: newThresholdCaches(dagconfig.DefinedDeployments),
}
// Create a genesis block node and block index index populated with it
@@ -118,7 +120,7 @@ func newTestDAG(params *dagconfig.Params) *BlockDAG {
// provided fields populated and fake values for the other fields.
func newTestNode(dag *BlockDAG, parents blockSet, blockVersion int32, bits uint32, timestamp mstime.Time) *blockNode {
// Make up a header and create a block node from it.
header := &appmessage.BlockHeader{
header := &domainmessage.BlockHeader{
Version: blockVersion,
ParentHashes: parents.hashes(),
Bits: bits,
@@ -166,7 +168,7 @@ func checkRuleError(gotErr, wantErr error) error {
return nil
}
func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parents ...*appmessage.MsgBlock) *appmessage.MsgBlock {
func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parents ...*domainmessage.MsgBlock) *domainmessage.MsgBlock {
parentHashes := make([]*daghash.Hash, len(parents))
for i, parent := range parents {
parentHashes[i] = parent.BlockHash()
@@ -174,7 +176,7 @@ func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parent
return PrepareAndProcessBlockForTest(t, dag, parentHashes, nil)
}
func nodeByMsgBlock(t *testing.T, dag *BlockDAG, block *appmessage.MsgBlock) *blockNode {
func nodeByMsgBlock(t *testing.T, dag *BlockDAG, block *domainmessage.MsgBlock) *blockNode {
node, ok := dag.index.LookupNode(block.BlockHash())
if !ok {
t.Fatalf("couldn't find block node with hash %s", block.BlockHash())

2143
blockdag/dag.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,15 +14,15 @@ import (
"time"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/pkg/errors"
)
func TestBlockCount(t *testing.T) {
@@ -214,7 +214,7 @@ func TestIsKnownBlock(t *testing.T) {
{hash: "732c891529619d43b5aeb3df42ba25dea483a8c0aded1cf585751ebabea28f29", want: true},
// Random hashes should not be available.
{hash: "1234567812345678123456781234567812345678123456781234567812345678", want: false},
{hash: "123", want: false},
}
for i, test := range tests {
@@ -237,52 +237,27 @@ func TestIsKnownBlock(t *testing.T) {
// the returned SequenceLocks are correct for each test instance.
func TestCalcSequenceLock(t *testing.T) {
netParams := &dagconfig.SimnetParams
dag, teardownFunc, err := DAGSetup("TestCalcSequenceLock", true, Config{
DAGParams: netParams,
})
if err != nil {
t.Fatalf("Error in DAGSetup: %+v", err)
}
defer teardownFunc()
blockVersion := int32(0x10000000)
// Generate enough synthetic blocks for the rest of the test
dag := newTestDAG(netParams)
node := dag.selectedTip()
blockTime := node.Header().Timestamp
numBlocksToGenerate := 5
for i := 0; i < numBlocksToGenerate; i++ {
parents := blockSetFromSlice(node)
block, err := PrepareBlockForTest(dag, parents.hashes(), nil)
if err != nil {
t.Fatalf("block No. %d got unexpected error from PrepareBlockForTest: %+v", i, err)
}
block.Header.Timestamp = blockTime.Add(time.Second)
utilBlock := util.NewBlock(block)
isOrphan, isDelayed, err := dag.ProcessBlock(utilBlock, BFNoPoWCheck)
if err != nil {
t.Fatalf("block No. %d got unexpected error from ProcessBlock: %+v", i, err)
}
if isOrphan || isDelayed {
t.Fatalf("Block No. %d is unexpectadly orphan: %t or delayed: %t", i, isOrphan, isDelayed)
}
var ok bool
node, ok = dag.index.LookupNode(block.BlockHash())
if !ok {
t.Errorf("Block No. %d not found in index after adding to dag", i)
}
blockTime = blockTime.Add(time.Second)
node = newTestNode(dag, blockSetFromSlice(node), blockVersion, 0, blockTime)
dag.index.AddNode(node)
dag.virtual.SetTips(blockSetFromSlice(node))
}
// Create a utxo view with a fake utxo for the inputs used in the
// transactions created below. This utxo is added such that it has an
// age of 4 blocks.
msgTx := appmessage.NewNativeMsgTx(appmessage.TxVersion, nil, []*appmessage.TxOut{{ScriptPubKey: nil, Value: 10}})
msgTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, nil, []*domainmessage.TxOut{{ScriptPubKey: nil, Value: 10}})
targetTx := util.NewTx(msgTx)
fullUTXOCacheSize := config.DefaultConfig().MaxUTXOCacheSize
db, teardown := prepareDatabaseForTest(t, "TestCalcSequenceLock")
defer teardown()
utxoSet := NewFullUTXOSetFromContext(db, fullUTXOCacheSize)
utxoSet := NewFullUTXOSet()
blueScore := uint64(numBlocksToGenerate) - 4
if isAccepted, err := utxoSet.AddTx(targetTx.MsgTx(), blueScore); err != nil {
t.Fatalf("AddTx unexpectedly failed. Error: %s", err)
@@ -295,7 +270,7 @@ func TestCalcSequenceLock(t *testing.T) {
// that the sequence lock heights are always calculated from the same
// point of view that they were originally calculated from for a given
// utxo. That is to say, the height prior to it.
utxo := appmessage.Outpoint{
utxo := domainmessage.Outpoint{
TxID: *targetTx.ID(),
Index: 0,
}
@@ -304,19 +279,19 @@ func TestCalcSequenceLock(t *testing.T) {
// Obtain the past median time from the PoV of the input created above.
// The past median time for the input is the past median time from the PoV
// of the block *prior* to the one that included it.
medianTime := node.RelativeAncestor(5).PastMedianTime().UnixMilliseconds()
medianTime := node.RelativeAncestor(5).PastMedianTime(dag).UnixMilliseconds()
// The median time calculated from the PoV of the best block in the
// test DAG. For unconfirmed inputs, this value will be used since
// the MTP will be calculated from the PoV of the yet-to-be-mined
// block.
nextMedianTime := node.PastMedianTime().UnixMilliseconds()
nextMedianTime := node.PastMedianTime(dag).UnixMilliseconds()
nextBlockBlueScore := int32(numBlocksToGenerate) + 1
// Add an additional transaction which will serve as our unconfirmed
// output.
unConfTx := appmessage.NewNativeMsgTx(appmessage.TxVersion, nil, []*appmessage.TxOut{{ScriptPubKey: nil, Value: 5}})
unConfUtxo := appmessage.Outpoint{
unConfTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, nil, []*domainmessage.TxOut{{ScriptPubKey: nil, Value: 5}})
unConfUtxo := domainmessage.Outpoint{
TxID: *unConfTx.TxID(),
Index: 0,
}
@@ -328,8 +303,9 @@ func TestCalcSequenceLock(t *testing.T) {
tests := []struct {
name string
tx *appmessage.MsgTx
tx *domainmessage.MsgTx
utxoSet UTXOSet
mempool bool
want *SequenceLock
}{
// A transaction with a single input with max sequence number.
@@ -337,7 +313,7 @@ func TestCalcSequenceLock(t *testing.T) {
// should be disabled.
{
name: "single input, max sequence number",
tx: appmessage.NewNativeMsgTx(1, []*appmessage.TxIn{{PreviousOutpoint: utxo, Sequence: appmessage.MaxTxInSequenceNum}}, nil),
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: domainmessage.MaxTxInSequenceNum}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: -1,
@@ -352,7 +328,7 @@ func TestCalcSequenceLock(t *testing.T) {
// the targeted block.
{
name: "single input, milliseconds lock time below time granularity",
tx: appmessage.NewNativeMsgTx(1, []*appmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 2)}}, nil),
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 2)}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime - 1,
@@ -364,7 +340,7 @@ func TestCalcSequenceLock(t *testing.T) {
// milliseconds after the median past time of the DAG.
{
name: "single input, 1048575 milliseconds after median time",
tx: appmessage.NewNativeMsgTx(1, []*appmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil),
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime + 1048575,
@@ -379,8 +355,8 @@ func TestCalcSequenceLock(t *testing.T) {
// latest lock that isn't disabled.
{
name: "multiple varied inputs",
tx: appmessage.NewNativeMsgTx(1,
[]*appmessage.TxIn{{
tx: domainmessage.NewNativeMsgTx(1,
[]*domainmessage.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 2621440),
}, {
@@ -389,12 +365,12 @@ func TestCalcSequenceLock(t *testing.T) {
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 5) |
appmessage.SequenceLockTimeDisabled,
domainmessage.SequenceLockTimeDisabled,
}},
nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime + (5 << appmessage.SequenceLockTimeGranularity) - 1,
Milliseconds: medianTime + (5 << domainmessage.SequenceLockTimeGranularity) - 1,
BlockBlueScore: int64(prevUtxoBlueScore) + 3,
},
},
@@ -404,7 +380,7 @@ func TestCalcSequenceLock(t *testing.T) {
// height of 2 meaning it can be included at height 3.
{
name: "single input, lock-time in blocks",
tx: appmessage.NewNativeMsgTx(1, []*appmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(false, 3)}}, nil),
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(false, 3)}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: -1,
@@ -416,7 +392,7 @@ func TestCalcSequenceLock(t *testing.T) {
// be the time further in the future.
{
name: "two inputs, lock-times in seconds",
tx: appmessage.NewNativeMsgTx(1, []*appmessage.TxIn{{
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 5242880),
}, {
@@ -425,7 +401,7 @@ func TestCalcSequenceLock(t *testing.T) {
}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime + (10 << appmessage.SequenceLockTimeGranularity) - 1,
Milliseconds: medianTime + (10 << domainmessage.SequenceLockTimeGranularity) - 1,
BlockBlueScore: -1,
},
},
@@ -435,8 +411,8 @@ func TestCalcSequenceLock(t *testing.T) {
// indicating it can be included at height 11.
{
name: "two inputs, lock-times in blocks",
tx: appmessage.NewNativeMsgTx(1,
[]*appmessage.TxIn{{
tx: domainmessage.NewNativeMsgTx(1,
[]*domainmessage.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 1),
}, {
@@ -455,8 +431,8 @@ func TestCalcSequenceLock(t *testing.T) {
// further into the future for both inputs should be chosen.
{
name: "four inputs, two lock-times in time, two lock-times in blocks",
tx: appmessage.NewNativeMsgTx(1,
[]*appmessage.TxIn{{
tx: domainmessage.NewNativeMsgTx(1,
[]*domainmessage.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 2621440),
}, {
@@ -472,7 +448,7 @@ func TestCalcSequenceLock(t *testing.T) {
nil),
utxoSet: utxoSet,
want: &SequenceLock{
Milliseconds: medianTime + (13 << appmessage.SequenceLockTimeGranularity) - 1,
Milliseconds: medianTime + (13 << domainmessage.SequenceLockTimeGranularity) - 1,
BlockBlueScore: int64(prevUtxoBlueScore) + 8,
},
},
@@ -484,8 +460,9 @@ func TestCalcSequenceLock(t *testing.T) {
// after that.
{
name: "single input, unconfirmed, lock-time in blocks",
tx: appmessage.NewNativeMsgTx(1, []*appmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil),
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil),
utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{
Milliseconds: -1,
BlockBlueScore: int64(nextBlockBlueScore) + 1,
@@ -496,8 +473,9 @@ func TestCalcSequenceLock(t *testing.T) {
// MTP of the *next* block.
{
name: "single input, unconfirmed, lock-time in milliseoncds",
tx: appmessage.NewNativeMsgTx(1, []*appmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil),
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil),
utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{
Milliseconds: nextMedianTime + 1048575,
BlockBlueScore: -1,
@@ -508,7 +486,7 @@ func TestCalcSequenceLock(t *testing.T) {
t.Logf("Running %v SequenceLock tests", len(tests))
for _, test := range tests {
utilTx := util.NewTx(test.tx)
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet)
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet, test.mempool)
if err != nil {
t.Fatalf("test '%s', unable to calc sequence lock: %v", test.name, err)
}
@@ -563,7 +541,7 @@ func TestCalcPastMedianTime(t *testing.T) {
}
for _, test := range tests {
millisecondsSinceGenesis := nodes[test.blockNumber].PastMedianTime().UnixMilliseconds() -
millisecondsSinceGenesis := nodes[test.blockNumber].PastMedianTime(dag).UnixMilliseconds() -
dag.genesis.Header().Timestamp.UnixMilliseconds()
if millisecondsSinceGenesis != test.expectedMillisecondsSinceGenesis {
@@ -695,7 +673,7 @@ func TestAcceptingInInit(t *testing.T) {
t.Fatalf("block %s does not exist in the DAG", testBlock.Hash())
}
if testNode.status != statusValid {
if testNode.status&statusValid == 0 {
t.Fatalf("testNode is unexpectedly invalid")
}
}
@@ -723,7 +701,7 @@ func TestConfirmations(t *testing.T) {
}
// Add a chain of blocks
chainBlocks := make([]*appmessage.MsgBlock, 5)
chainBlocks := make([]*domainmessage.MsgBlock, 5)
chainBlocks[0] = dag.Params.GenesisBlock
for i := uint32(1); i < 5; i++ {
chainBlocks[i] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[i-1])
@@ -743,7 +721,7 @@ func TestConfirmations(t *testing.T) {
}
}
branchingBlocks := make([]*appmessage.MsgBlock, 2)
branchingBlocks := make([]*domainmessage.MsgBlock, 2)
// Add two branching blocks
branchingBlocks[0] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[1])
branchingBlocks[1] = prepareAndProcessBlockByParentMsgBlocks(t, dag, branchingBlocks[0])
@@ -760,7 +738,7 @@ func TestConfirmations(t *testing.T) {
}
// Check that each of the tips has a 0 confirmations
tips := dag.tips
tips := dag.virtual.tips()
for tip := range tips {
tipConfirmations, err := dag.blockConfirmations(tip)
if err != nil {
@@ -816,7 +794,7 @@ func TestAcceptingBlock(t *testing.T) {
defer teardownFunc()
dag.TestSetCoinbaseMaturity(0)
acceptingBlockByMsgBlock := func(block *appmessage.MsgBlock) (*blockNode, error) {
acceptingBlockByMsgBlock := func(block *domainmessage.MsgBlock) (*blockNode, error) {
node := nodeByMsgBlock(t, dag, block)
return dag.acceptingBlock(node)
}
@@ -832,7 +810,7 @@ func TestAcceptingBlock(t *testing.T) {
}
numChainBlocks := uint32(10)
chainBlocks := make([]*appmessage.MsgBlock, numChainBlocks)
chainBlocks := make([]*domainmessage.MsgBlock, numChainBlocks)
chainBlocks[0] = dag.Params.GenesisBlock
for i := uint32(1); i <= numChainBlocks-1; i++ {
chainBlocks[i] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[i-1])
@@ -929,6 +907,119 @@ func TestAcceptingBlock(t *testing.T) {
}
}
func TestFinalizeNodesBelowFinalityPoint(t *testing.T) {
testFinalizeNodesBelowFinalityPoint(t, true)
testFinalizeNodesBelowFinalityPoint(t, false)
}
func testFinalizeNodesBelowFinalityPoint(t *testing.T, deleteDiffData bool) {
params := dagconfig.SimnetParams
params.K = 1
dag, teardownFunc, err := DAGSetup("testFinalizeNodesBelowFinalityPoint", true, Config{
DAGParams: &params,
})
if err != nil {
t.Fatalf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
blockVersion := int32(0x10000000)
blockTime := dag.genesis.Header().Timestamp
flushUTXODiffStore := func() {
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
t.Fatalf("Failed to open database transaction: %s", err)
}
defer dbTx.RollbackUnlessClosed()
err = dag.utxoDiffStore.flushToDB(dbTx)
if err != nil {
t.Fatalf("Error flushing utxoDiffStore data to DB: %s", err)
}
dag.utxoDiffStore.clearDirtyEntries()
err = dbTx.Commit()
if err != nil {
t.Fatalf("Failed to commit database transaction: %s", err)
}
}
addNode := func(parent *blockNode) *blockNode {
blockTime = blockTime.Add(time.Second)
node := newTestNode(dag, blockSetFromSlice(parent), blockVersion, 0, blockTime)
node.updateParentsChildren()
dag.index.AddNode(node)
// Put dummy diff data in dag.utxoDiffStore
err := dag.utxoDiffStore.setBlockDiff(node, NewUTXODiff())
if err != nil {
t.Fatalf("setBlockDiff: %s", err)
}
flushUTXODiffStore()
return node
}
finalityInterval := dag.FinalityInterval()
nodes := make([]*blockNode, 0, finalityInterval)
currentNode := dag.genesis
nodes = append(nodes, currentNode)
for i := uint64(0); i <= finalityInterval*2; i++ {
currentNode = addNode(currentNode)
nodes = append(nodes, currentNode)
}
// Manually set the last finality point
dag.lastFinalityPoint = nodes[finalityInterval-1]
// Don't unload diffData
currentDifference := maxBlueScoreDifferenceToKeepLoaded
maxBlueScoreDifferenceToKeepLoaded = math.MaxUint64
defer func() { maxBlueScoreDifferenceToKeepLoaded = currentDifference }()
dag.finalizeNodesBelowFinalityPoint(deleteDiffData)
flushUTXODiffStore()
for _, node := range nodes[:finalityInterval-1] {
if !node.isFinalized {
t.Errorf("Node with blue score %d expected to be finalized", node.blueScore)
}
if _, ok := dag.utxoDiffStore.loaded[node]; deleteDiffData && ok {
t.Errorf("The diff data of node with blue score %d should have been unloaded if deleteDiffData is %T", node.blueScore, deleteDiffData)
} else if !deleteDiffData && !ok {
t.Errorf("The diff data of node with blue score %d shouldn't have been unloaded if deleteDiffData is %T", node.blueScore, deleteDiffData)
}
_, err := dag.utxoDiffStore.diffDataFromDB(node.hash)
exists := !dbaccess.IsNotFoundError(err)
if exists && err != nil {
t.Errorf("diffDataFromDB: %s", err)
continue
}
if deleteDiffData && exists {
t.Errorf("The diff data of node with blue score %d should have been deleted from the database if deleteDiffData is %T", node.blueScore, deleteDiffData)
continue
}
if !deleteDiffData && !exists {
t.Errorf("The diff data of node with blue score %d shouldn't have been deleted from the database if deleteDiffData is %T", node.blueScore, deleteDiffData)
continue
}
}
for _, node := range nodes[finalityInterval-1:] {
if node.isFinalized {
t.Errorf("Node with blue score %d wasn't expected to be finalized", node.blueScore)
}
if _, ok := dag.utxoDiffStore.loaded[node]; !ok {
t.Errorf("The diff data of node with blue score %d shouldn't have been unloaded", node.blueScore)
}
if diffData, err := dag.utxoDiffStore.diffDataFromDB(node.hash); err != nil {
t.Errorf("diffDataFromDB: %s", err)
} else if diffData == nil {
t.Errorf("The diff data of node with blue score %d shouldn't have been deleted from the database", node.blueScore)
}
}
}
func TestDAGIndexFailedStatus(t *testing.T) {
params := dagconfig.SimnetParams
dag, teardownFunc, err := DAGSetup("TestDAGIndexFailedStatus", true, Config{
@@ -939,17 +1030,11 @@ func TestDAGIndexFailedStatus(t *testing.T) {
}
defer teardownFunc()
// Create a block with non-finalized transaction so that it's flagged as invalid
coinbaseTx := appmessage.NewSubnetworkMsgTx(appmessage.TxVersion, []*appmessage.TxIn{},
[]*appmessage.TxOut{}, subnetworkid.SubnetworkIDCoinbase, 0, []byte{})
invalidTxIn := appmessage.NewTxIn(appmessage.NewOutpoint(dag.Params.GenesisBlock.Transactions[0].TxID(), 0), nil)
invalidTxIn.Sequence = 0
invalidTx := appmessage.NewNativeMsgTx(appmessage.TxVersion, []*appmessage.TxIn{invalidTxIn}, []*appmessage.TxOut{})
invalidTx.LockTime = math.MaxUint64
txs := []*util.Tx{util.NewTx(coinbaseTx), util.NewTx(invalidTx)}
invalidCbTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{}, []*domainmessage.TxOut{}, subnetworkid.SubnetworkIDCoinbase, 0, []byte{})
txs := []*util.Tx{util.NewTx(invalidCbTx)}
hashMerkleRoot := BuildHashMerkleTreeStore(txs).Root()
invalidMsgBlock := appmessage.NewMsgBlock(
appmessage.NewBlockHeader(
invalidMsgBlock := domainmessage.NewMsgBlock(
domainmessage.NewBlockHeader(
1,
[]*daghash.Hash{params.GenesisHash}, hashMerkleRoot,
&daghash.Hash{},
@@ -957,8 +1042,7 @@ func TestDAGIndexFailedStatus(t *testing.T) {
dag.genesis.bits,
0),
)
invalidMsgBlock.AddTransaction(coinbaseTx)
invalidMsgBlock.AddTransaction(invalidTx)
invalidMsgBlock.AddTransaction(invalidCbTx)
invalidBlock := util.NewBlock(invalidMsgBlock)
isOrphan, isDelayed, err := dag.ProcessBlock(invalidBlock, BFNoPoWCheck)
@@ -978,17 +1062,16 @@ func TestDAGIndexFailedStatus(t *testing.T) {
if !ok {
t.Fatalf("invalidBlockNode wasn't added to the block index as expected")
}
if invalidBlockNode.status != statusValidateFailed {
if invalidBlockNode.status&statusValidateFailed != statusValidateFailed {
t.Fatalf("invalidBlockNode status to have %b flags raised (got: %b)", statusValidateFailed, invalidBlockNode.status)
}
invalidMsgBlockChild := appmessage.NewMsgBlock(
appmessage.NewBlockHeader(1, []*daghash.Hash{
invalidMsgBlockChild := domainmessage.NewMsgBlock(
domainmessage.NewBlockHeader(1, []*daghash.Hash{
invalidBlock.Hash(),
}, hashMerkleRoot, &daghash.Hash{}, &daghash.Hash{}, dag.genesis.bits, 0),
)
invalidMsgBlockChild.AddTransaction(coinbaseTx)
invalidMsgBlockChild.AddTransaction(invalidTx)
invalidMsgBlockChild.AddTransaction(invalidCbTx)
invalidBlockChild := util.NewBlock(invalidMsgBlockChild)
isOrphan, isDelayed, err = dag.ProcessBlock(invalidBlockChild, BFNoPoWCheck)
@@ -1008,17 +1091,16 @@ func TestDAGIndexFailedStatus(t *testing.T) {
if !ok {
t.Fatalf("invalidBlockChild wasn't added to the block index as expected")
}
if invalidBlockChildNode.status != statusInvalidAncestor {
if invalidBlockChildNode.status&statusInvalidAncestor != statusInvalidAncestor {
t.Fatalf("invalidBlockNode status to have %b flags raised (got %b)", statusInvalidAncestor, invalidBlockChildNode.status)
}
invalidMsgBlockGrandChild := appmessage.NewMsgBlock(
appmessage.NewBlockHeader(1, []*daghash.Hash{
invalidMsgBlockGrandChild := domainmessage.NewMsgBlock(
domainmessage.NewBlockHeader(1, []*daghash.Hash{
invalidBlockChild.Hash(),
}, hashMerkleRoot, &daghash.Hash{}, &daghash.Hash{}, dag.genesis.bits, 0),
)
invalidMsgBlockGrandChild.AddTransaction(coinbaseTx)
invalidMsgBlockGrandChild.AddTransaction(invalidTx)
invalidMsgBlockGrandChild.AddTransaction(invalidCbTx)
invalidBlockGrandChild := util.NewBlock(invalidMsgBlockGrandChild)
isOrphan, isDelayed, err = dag.ProcessBlock(invalidBlockGrandChild, BFNoPoWCheck)
@@ -1037,60 +1119,40 @@ func TestDAGIndexFailedStatus(t *testing.T) {
if !ok {
t.Fatalf("invalidBlockGrandChild wasn't added to the block index as expected")
}
if invalidBlockGrandChildNode.status != statusInvalidAncestor {
if invalidBlockGrandChildNode.status&statusInvalidAncestor != statusInvalidAncestor {
t.Fatalf("invalidBlockGrandChildNode status to have %b flags raised (got %b)", statusInvalidAncestor, invalidBlockGrandChildNode.status)
}
}
// testProcessBlockStatus submits the given block, and makes sure this block has got the expected status
func testProcessBlockStatus(
t *testing.T, testName string, dag *BlockDAG, block *appmessage.MsgBlock, expectedStatus blockStatus) {
isOrphan, isDelayed, err := dag.ProcessBlock(util.NewBlock(block), BFNoPoWCheck)
if err != nil {
t.Fatalf("%s: Error submitting block: %+v", testName, err)
func TestIsDAGCurrentMaxDiff(t *testing.T) {
netParams := []*dagconfig.Params{
&dagconfig.MainnetParams,
&dagconfig.TestnetParams,
&dagconfig.DevnetParams,
&dagconfig.RegressionNetParams,
&dagconfig.SimnetParams,
}
if isDelayed {
t.Fatalf("%s: ProcessBlock: block is too far in the future", testName)
}
if isOrphan {
t.Fatalf("%s: ProcessBlock: block got unexpectedly orphaned", testName)
}
node, ok := dag.index.LookupNode(block.BlockHash())
if !ok {
t.Fatalf("%s: Error locating block %s after processing it", testName, block.BlockHash())
}
actualStatus := dag.index.BlockNodeStatus(node)
if actualStatus != expectedStatus {
t.Errorf("%s: Expected block status: '%s' but got '%s'", testName, expectedStatus, actualStatus)
for _, params := range netParams {
if params.FinalityDuration < isDAGCurrentMaxDiff*params.TargetTimePerBlock {
t.Errorf("in %s, a DAG can be considered current even if it's below the finality point", params.Name)
}
}
}
func testProcessBlockRuleError(t *testing.T, testName string, dag *BlockDAG, block *appmessage.MsgBlock, expectedRuleErr error) {
func testProcessBlockRuleError(t *testing.T, dag *BlockDAG, block *domainmessage.MsgBlock, expectedRuleErr error) {
isOrphan, isDelayed, err := dag.ProcessBlock(util.NewBlock(block), BFNoPoWCheck)
err = checkRuleError(err, expectedRuleErr)
if err != nil {
t.Errorf("%s: checkRuleError: %s", err, testName)
t.Errorf("checkRuleError: %s", err)
}
if isDelayed {
t.Fatalf("%s: ProcessBlock: block is too far in the future", testName)
t.Fatalf("ProcessBlock: block " +
"is too far in the future")
}
if isOrphan {
t.Fatalf("%s: ProcessBlock: block got unexpectedly orphaned", testName)
}
}
// makeNextSelectedTip plays with block's nonce until its hash is smaller than dag's selectedTip
// It is the callers responsibility to make sure block's blue score is equal to selected tip's
func makeNextSelectedTip(dag *BlockDAG, block *appmessage.MsgBlock) {
selectedTip := dag.selectedTip()
for daghash.Less(block.BlockHash(), selectedTip.hash) {
block.Header.Nonce++
t.Fatalf("ProcessBlock: block got unexpectedly orphaned")
}
}
@@ -1113,26 +1175,26 @@ func TestDoubleSpends(t *testing.T) {
if err != nil {
t.Fatalf("Failed to build signature script: %s", err)
}
txIn := &appmessage.TxIn{
PreviousOutpoint: appmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
txIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: appmessage.MaxTxInSequenceNum,
Sequence: domainmessage.MaxTxInSequenceNum,
}
txOut := &appmessage.TxOut{
txOut := &domainmessage.TxOut{
ScriptPubKey: OpTrueScript,
Value: uint64(1),
}
tx1 := appmessage.NewNativeMsgTx(appmessage.TxVersion, []*appmessage.TxIn{txIn}, []*appmessage.TxOut{txOut})
tx1 := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut})
doubleSpendTxOut := &appmessage.TxOut{
doubleSpendTxOut := &domainmessage.TxOut{
ScriptPubKey: OpTrueScript,
Value: uint64(2),
}
doubleSpendTx1 := appmessage.NewNativeMsgTx(appmessage.TxVersion, []*appmessage.TxIn{txIn}, []*appmessage.TxOut{doubleSpendTxOut})
doubleSpendTx1 := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{doubleSpendTxOut})
blockWithTx1 := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*appmessage.MsgTx{tx1})
blockWithTx1 := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*domainmessage.MsgTx{tx1})
// Check that a block will be disqualified if it has a transaction that already exists in its past.
// Check that a block will be rejected if it has a transaction that already exists in its past.
anotherBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{blockWithTx1.BlockHash()}, nil)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
@@ -1146,9 +1208,9 @@ func TestDoubleSpends(t *testing.T) {
}
anotherBlockWithTx1.Header.HashMerkleRoot = BuildHashMerkleTreeStore(anotherBlockWithTx1UtilTxs).Root()
testProcessBlockStatus(t, "anotherBlockWithTx1", dag, anotherBlockWithTx1, statusDisqualifiedFromChain)
testProcessBlockRuleError(t, dag, anotherBlockWithTx1, ruleError(ErrOverwriteTx, ""))
// Check that a block will be disqualified if it has a transaction that double spends
// Check that a block will be rejected if it has a transaction that double spends
// a transaction from its past.
blockWithDoubleSpendForTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{blockWithTx1.BlockHash()}, nil)
if err != nil {
@@ -1163,18 +1225,16 @@ func TestDoubleSpends(t *testing.T) {
}
blockWithDoubleSpendForTx1.Header.HashMerkleRoot = BuildHashMerkleTreeStore(blockWithDoubleSpendForTx1UtilTxs).Root()
testProcessBlockStatus(t, "blockWithDoubleSpendForTx1", dag, blockWithDoubleSpendForTx1, statusDisqualifiedFromChain)
testProcessBlockRuleError(t, dag, blockWithDoubleSpendForTx1, ruleError(ErrMissingTxOut, ""))
blockInAnticoneOfBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*appmessage.MsgTx{doubleSpendTx1})
blockInAnticoneOfBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*domainmessage.MsgTx{doubleSpendTx1})
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
makeNextSelectedTip(dag, blockInAnticoneOfBlockWithTx1)
// Check that a block will not get disqualified if it has a transaction that double spends
// Check that a block will not get rejected if it has a transaction that double spends
// a transaction from its anticone.
testProcessBlockStatus(t, "blockInAnticoneOfBlockWithTx1", dag, blockInAnticoneOfBlockWithTx1, statusValid)
testProcessBlockRuleError(t, dag, blockInAnticoneOfBlockWithTx1, nil)
// Check that a block will be rejected if it has two transactions that spend the same UTXO.
blockWithDoubleSpendWithItself, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, nil)
@@ -1190,8 +1250,7 @@ func TestDoubleSpends(t *testing.T) {
}
blockWithDoubleSpendWithItself.Header.HashMerkleRoot = BuildHashMerkleTreeStore(blockWithDoubleSpendWithItselfUtilTxs).Root()
testProcessBlockRuleError(t, "blockWithDoubleSpendWithItself", dag,
blockWithDoubleSpendWithItself, ruleError(ErrDoubleSpendInSameBlock, ""))
testProcessBlockRuleError(t, dag, blockWithDoubleSpendWithItself, ruleError(ErrDoubleSpendInSameBlock, ""))
// Check that a block will be rejected if it has the same transaction twice.
blockWithDuplicateTransaction, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, nil)
@@ -1206,8 +1265,7 @@ func TestDoubleSpends(t *testing.T) {
blockWithDuplicateTransactionUtilTxs[i] = util.NewTx(tx)
}
blockWithDuplicateTransaction.Header.HashMerkleRoot = BuildHashMerkleTreeStore(blockWithDuplicateTransactionUtilTxs).Root()
testProcessBlockRuleError(t, "blockWithDuplicateTransaction", dag,
blockWithDuplicateTransaction, ruleError(ErrDuplicateTx, ""))
testProcessBlockRuleError(t, dag, blockWithDuplicateTransaction, ruleError(ErrDuplicateTx, ""))
}
func TestUTXOCommitment(t *testing.T) {
@@ -1224,7 +1282,7 @@ func TestUTXOCommitment(t *testing.T) {
resetExtraNonceForTest()
createTx := func(txToSpend *appmessage.MsgTx) *appmessage.MsgTx {
createTx := func(txToSpend *domainmessage.MsgTx) *domainmessage.MsgTx {
scriptPubKey, err := txscript.PayToScriptHashScript(OpTrueScript)
if err != nil {
t.Fatalf("TestUTXOCommitment: failed to build script pub key: %s", err)
@@ -1233,16 +1291,16 @@ func TestUTXOCommitment(t *testing.T) {
if err != nil {
t.Fatalf("TestUTXOCommitment: failed to build signature script: %s", err)
}
txIn := &appmessage.TxIn{
PreviousOutpoint: appmessage.Outpoint{TxID: *txToSpend.TxID(), Index: 0},
txIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *txToSpend.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: appmessage.MaxTxInSequenceNum,
Sequence: domainmessage.MaxTxInSequenceNum,
}
txOut := &appmessage.TxOut{
txOut := &domainmessage.TxOut{
ScriptPubKey: scriptPubKey,
Value: uint64(1),
}
return appmessage.NewNativeMsgTx(appmessage.TxVersion, []*appmessage.TxIn{txIn}, []*appmessage.TxOut{txOut})
return domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut})
}
// Build the following DAG:
@@ -1258,12 +1316,12 @@ func TestUTXOCommitment(t *testing.T) {
// Block C:
txSpendBlockACoinbase := createTx(blockA.Transactions[0])
blockCTxs := []*appmessage.MsgTx{txSpendBlockACoinbase}
blockCTxs := []*domainmessage.MsgTx{txSpendBlockACoinbase}
blockC := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockA.BlockHash()}, blockCTxs)
// Block D:
txSpendTxInBlockC := createTx(txSpendBlockACoinbase)
blockDTxs := []*appmessage.MsgTx{txSpendTxInBlockC}
blockDTxs := []*domainmessage.MsgTx{txSpendTxInBlockC}
blockD := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockB.BlockHash(), blockC.BlockHash()}, blockDTxs)
// Get the pastUTXO of blockD
@@ -1276,7 +1334,7 @@ func TestUTXOCommitment(t *testing.T) {
// Build a Multiset for block D
multiset := secp256k1.NewMultiset()
for outpoint, entry := range blockDPastDiffUTXOSet.base.utxoCache {
for outpoint, entry := range blockDPastDiffUTXOSet.base.utxoCollection {
var err error
multiset, err = addUTXOToMultiset(multiset, entry, &outpoint)
if err != nil {
@@ -1331,7 +1389,7 @@ func TestPastUTXOMultiSet(t *testing.T) {
if !ok {
t.Fatalf("TestPastUTXOMultiSet: blockNode for blockC not found")
}
blockCSelectedParentMultiset, err := blockNodeC.selectedParentMultiset()
blockCSelectedParentMultiset, err := blockNodeC.selectedParentMultiset(dag)
if err != nil {
t.Fatalf("TestPastUTXOMultiSet: selectedParentMultiset unexpectedly failed: %s", err)
}
@@ -1344,7 +1402,7 @@ func TestPastUTXOMultiSet(t *testing.T) {
PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockC.BlockHash()}, nil)
// Get blockC's selectedParentMultiset again
blockCSelectedParentMultiSetAfterAnotherBlock, err := blockNodeC.selectedParentMultiset()
blockCSelectedParentMultiSetAfterAnotherBlock, err := blockNodeC.selectedParentMultiset(dag)
if err != nil {
t.Fatalf("TestPastUTXOMultiSet: selectedParentMultiset unexpectedly failed: %s", err)
}

View File

@@ -11,11 +11,11 @@ import (
"fmt"
"io"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/binaryserializer"
"github.com/kaspanet/kaspad/util/daghash"
@@ -49,29 +49,22 @@ func IsNotInDAGErr(err error) bool {
// keys will be iterated in an ascending order by the outpoint index.
var outpointIndexByteOrder = binary.BigEndian
func serializeOutpoint(w io.Writer, outpoint *appmessage.Outpoint) error {
func serializeOutpoint(w io.Writer, outpoint *domainmessage.Outpoint) error {
_, err := w.Write(outpoint.TxID[:])
if err != nil {
return err
}
var buf [4]byte
outpointIndexByteOrder.PutUint32(buf[:], outpoint.Index)
_, err = w.Write(buf[:])
if err != nil {
return errors.WithStack(err)
}
return nil
return binaryserializer.PutUint32(w, outpointIndexByteOrder, outpoint.Index)
}
var outpointSerializeSize = daghash.TxIDSize + 4
// deserializeOutpoint decodes an outpoint from the passed serialized byte
// slice into a new appmessage.Outpoint using a format that is suitable for long-
// slice into a new domainmessage.Outpoint using a format that is suitable for long-
// term storage. This format is described in detail above.
func deserializeOutpoint(r io.Reader) (*appmessage.Outpoint, error) {
outpoint := &appmessage.Outpoint{}
func deserializeOutpoint(r io.Reader) (*domainmessage.Outpoint, error) {
outpoint := &domainmessage.Outpoint{}
_, err := r.Read(outpoint.TxID[:])
if err != nil {
return nil, err
@@ -133,10 +126,9 @@ func updateUTXOSet(dbContext dbaccess.Context, virtualUTXODiff *UTXODiff) error
}
type dagState struct {
TipHashes []*daghash.Hash
VirtualParentsHashes []*daghash.Hash
ValidTipHashes []*daghash.Hash
LocalSubnetworkID *subnetworkid.SubnetworkID
TipHashes []*daghash.Hash
LastFinalityPoint *daghash.Hash
LocalSubnetworkID *subnetworkid.SubnetworkID
}
// serializeDAGState returns the serialization of the DAG state.
@@ -173,10 +165,9 @@ func saveDAGState(dbContext dbaccess.Context, state *dagState) error {
// genesis block and the node's local subnetwork id.
func (dag *BlockDAG) createDAGState(localSubnetworkID *subnetworkid.SubnetworkID) error {
return saveDAGState(dag.databaseContext, &dagState{
TipHashes: []*daghash.Hash{dag.Params.GenesisHash},
VirtualParentsHashes: []*daghash.Hash{dag.Params.GenesisHash},
ValidTipHashes: []*daghash.Hash{dag.Params.GenesisHash},
LocalSubnetworkID: localSubnetworkID,
TipHashes: []*daghash.Hash{dag.Params.GenesisHash},
LastFinalityPoint: dag.Params.GenesisHash,
LocalSubnetworkID: localSubnetworkID,
})
}
@@ -211,6 +202,12 @@ func (dag *BlockDAG) initDAGState() error {
return err
}
log.Debugf("Loading UTXO set...")
fullUTXOCollection, err := dag.initUTXOSet()
if err != nil {
return err
}
log.Debugf("Loading reachability data...")
err = dag.reachabilityTree.init(dag.databaseContext)
if err != nil {
@@ -223,12 +220,27 @@ func (dag *BlockDAG) initDAGState() error {
return err
}
log.Debugf("Applying the loaded utxoCollection to the virtual block...")
dag.virtual.utxoSet, err = newFullUTXOSetFromUTXOCollection(fullUTXOCollection)
if err != nil {
return errors.Wrap(err, "Error loading UTXOSet")
}
log.Debugf("Applying the stored tips to the virtual block...")
err = dag.initTipsAndVirtualParents(dagState)
err = dag.initVirtualBlockTips(dagState)
if err != nil {
return err
}
log.Debugf("Setting the last finality point...")
var ok bool
dag.lastFinalityPoint, ok = dag.index.LookupNode(dagState.LastFinalityPoint)
if !ok {
return errors.Errorf("finality point block %s "+
"does not exist in the DAG", dagState.LastFinalityPoint)
}
dag.finalizeNodesBelowFinalityPoint(false)
log.Debugf("Processing unprocessed blockNodes...")
err = dag.processUnprocessedBlockNodes(unprocessedBlockNodes)
if err != nil {
@@ -338,28 +350,17 @@ func (dag *BlockDAG) initUTXOSet() (fullUTXOCollection utxoCollection, err error
return fullUTXOCollection, nil
}
func (dag *BlockDAG) initTipsAndVirtualParents(state *dagState) error {
tips, err := dag.index.LookupNodes(state.TipHashes)
if err != nil {
return errors.Wrapf(err, "Error loading tips")
func (dag *BlockDAG) initVirtualBlockTips(state *dagState) error {
tips := newBlockSet()
for _, tipHash := range state.TipHashes {
tip, ok := dag.index.LookupNode(tipHash)
if !ok {
return errors.Errorf("cannot find "+
"DAG tip %s in block index", state.TipHashes)
}
tips.add(tip)
}
dag.tips = blockSetFromSlice(tips...)
validTips, err := dag.index.LookupNodes(state.ValidTipHashes)
if err != nil {
return errors.Wrapf(err, "Error loading tips")
}
dag.validTips = blockSetFromSlice(validTips...)
virtualParents, err := dag.index.LookupNodes(state.VirtualParentsHashes)
if err != nil {
return errors.Wrapf(err, "Error loading tips")
}
dag.virtual.blockNode, _ = dag.newBlockNode(nil, blockSetFromSlice(virtualParents...))
// call updateSelectedParentSet with genesis as oldSelectedParent, so that the selectedParentSet is fully calculated
_ = dag.virtual.updateSelectedParentSet(dag.genesis)
dag.virtual.SetTips(tips)
return nil
}
@@ -409,14 +410,13 @@ func (dag *BlockDAG) processUnprocessedBlockNodes(unprocessedBlockNodes []*block
func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
buffer := bytes.NewReader(blockRow)
var header appmessage.BlockHeader
var header domainmessage.BlockHeader
err := header.Deserialize(buffer)
if err != nil {
return nil, err
}
node := &blockNode{
dag: dag,
hash: header.BlockHash(),
version: header.Version,
bits: header.Bits,
@@ -464,7 +464,7 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
return nil, err
}
bluesCount, err := appmessage.ReadVarInt(buffer)
bluesCount, err := domainmessage.ReadVarInt(buffer)
if err != nil {
return nil, err
}
@@ -483,26 +483,7 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
}
}
redsCount, err := appmessage.ReadVarInt(buffer)
if err != nil {
return nil, err
}
node.reds = make([]*blockNode, redsCount)
for i := uint64(0); i < redsCount; i++ {
hash := &daghash.Hash{}
if _, err := io.ReadFull(buffer, hash[:]); err != nil {
return nil, err
}
var ok bool
node.reds[i], ok = dag.index.LookupNode(hash)
if !ok {
return nil, errors.Errorf("block %s does not exist in the DAG", selectedParentHash)
}
}
bluesAnticoneSizesLen, err := appmessage.ReadVarInt(buffer)
bluesAnticoneSizesLen, err := domainmessage.ReadVarInt(buffer)
if err != nil {
return nil, err
}
@@ -546,7 +527,7 @@ func storeBlock(dbContext *dbaccess.TxContext, block *util.Block) error {
}
func serializeBlockNode(node *blockNode) ([]byte, error) {
w := bytes.NewBuffer(make([]byte, 0, appmessage.MaxBlockHeaderPayload+1))
w := bytes.NewBuffer(make([]byte, 0, domainmessage.MaxBlockHeaderPayload+1))
header := node.Header()
err := header.Serialize(w)
if err != nil {
@@ -573,7 +554,7 @@ func serializeBlockNode(node *blockNode) ([]byte, error) {
return nil, err
}
err = appmessage.WriteVarInt(w, uint64(len(node.blues)))
err = domainmessage.WriteVarInt(w, uint64(len(node.blues)))
if err != nil {
return nil, err
}
@@ -585,19 +566,7 @@ func serializeBlockNode(node *blockNode) ([]byte, error) {
}
}
err = appmessage.WriteVarInt(w, uint64(len(node.reds)))
if err != nil {
return nil, err
}
for _, red := range node.reds {
_, err = w.Write(red.hash[:])
if err != nil {
return nil, err
}
}
err = appmessage.WriteVarInt(w, uint64(len(node.bluesAnticoneSizes)))
err = domainmessage.WriteVarInt(w, uint64(len(node.bluesAnticoneSizes)))
if err != nil {
return nil, err
}
@@ -693,16 +662,3 @@ func (dag *BlockDAG) BlockHashesFrom(lowHash *daghash.Hash, limit int) ([]*dagha
return blockHashes, nil
}
func (dag *BlockDAG) fetchBlueBlocks(node *blockNode) ([]*util.Block, error) {
blueBlocks := make([]*util.Block, len(node.blues))
for i, blueBlockNode := range node.blues {
blueBlock, err := dag.fetchBlockByHash(blueBlockNode.hash)
if err != nil {
return nil, err
}
blueBlocks[i] = blueBlock
}
return blueBlocks, nil
}

View File

@@ -7,11 +7,10 @@ package blockdag
import (
"bytes"
"encoding/hex"
"github.com/pkg/errors"
"reflect"
"testing"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/util/daghash"
)
@@ -186,20 +185,18 @@ func TestDAGStateSerialization(t *testing.T) {
{
name: "genesis",
state: &dagState{
TipHashes: []*daghash.Hash{newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")},
ValidTipHashes: []*daghash.Hash{newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")},
VirtualParentsHashes: []*daghash.Hash{newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")},
TipHashes: []*daghash.Hash{newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")},
LastFinalityPoint: newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
},
serialized: []byte(`{"TipHashes":[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]],"VirtualParentsHashes":[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]],"ValidTipHashes":[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]],"LocalSubnetworkID":null}`),
serialized: []byte("{\"TipHashes\":[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]],\"LastFinalityPoint\":[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0],\"LocalSubnetworkID\":null}"),
},
{
name: "block 1",
state: &dagState{
TipHashes: []*daghash.Hash{newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")},
ValidTipHashes: []*daghash.Hash{newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")},
VirtualParentsHashes: []*daghash.Hash{newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")},
TipHashes: []*daghash.Hash{newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")},
LastFinalityPoint: newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
},
serialized: []byte(`{"TipHashes":[[72,96,235,24,191,27,22,32,227,126,148,144,252,138,66,117,20,65,111,215,81,89,171,134,104,142,154,131,0,0,0,0]],"VirtualParentsHashes":[[72,96,235,24,191,27,22,32,227,126,148,144,252,138,66,117,20,65,111,215,81,89,171,134,104,142,154,131,0,0,0,0]],"ValidTipHashes":[[72,96,235,24,191,27,22,32,227,126,148,144,252,138,66,117,20,65,111,215,81,89,171,134,104,142,154,131,0,0,0,0]],"LocalSubnetworkID":null}`),
serialized: []byte("{\"TipHashes\":[[72,96,235,24,191,27,22,32,227,126,148,144,252,138,66,117,20,65,111,215,81,89,171,134,104,142,154,131,0,0,0,0]],\"LastFinalityPoint\":[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0],\"LocalSubnetworkID\":null}"),
},
}

View File

@@ -15,7 +15,7 @@ import (
// block given its bluest parent.
func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime mstime.Time) uint32 {
// Genesis block.
if dag.Params.DisableDifficultyAdjustment || bluestParent == nil || bluestParent.blueScore < dag.difficultyAdjustmentWindowSize+1 {
if bluestParent == nil || bluestParent.blueScore < dag.difficultyAdjustmentWindowSize+1 {
return dag.powMaxBits
}

View File

@@ -5,13 +5,12 @@
package blockdag
import (
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util/mstime"
"math/big"
"testing"
"time"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/util"
)
@@ -81,7 +80,7 @@ func TestCalcWork(t *testing.T) {
}
func TestDifficulty(t *testing.T) {
params := dagconfig.MainnetParams
params := dagconfig.SimnetParams
params.K = 1
params.DifficultyAdjustmentWindowSize = 264
dag, teardownFunc, err := DAGSetup("TestDifficulty", true, Config{
@@ -136,7 +135,7 @@ func TestDifficulty(t *testing.T) {
t.Fatalf("As long as the block rate remains the same, the difficulty shouldn't change")
}
}
nodeInThePast := addNode(blockSetFromSlice(tip), tip.PastMedianTime())
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")
}
@@ -158,7 +157,7 @@ func TestDifficulty(t *testing.T) {
// Increase block rate to increase difficulty
for i := uint64(0); i < dag.difficultyAdjustmentWindowSize; i++ {
tip = addNode(blockSetFromSlice(tip), tip.PastMedianTime())
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")
}
@@ -204,7 +203,7 @@ func TestDifficulty(t *testing.T) {
redChainTip := splitNode
for i := 0; i < 10; i++ {
redChainTip = addNode(blockSetFromSlice(redChainTip), redChainTip.PastMedianTime())
redChainTip = addNode(blockSetFromSlice(redChainTip), redChainTip.PastMedianTime(dag))
}
tipWithRedPast := addNode(blockSetFromSlice(redChainTip, blueTip), zeroTime)
tipWithoutRedPast := addNode(blockSetFromSlice(blueTip), zeroTime)

View File

@@ -203,9 +203,6 @@ const (
// is also an ancestor of another parent
ErrInvalidParentsRelation
// ErrTooManyParents indicates that a block points to more then `MaxNumParentBlocks` parents
ErrTooManyParents
// ErrDelayedBlockIsNotAllowed indicates that a block with a delayed timestamp was
// submitted with BFDisallowDelay flag raised.
ErrDelayedBlockIsNotAllowed
@@ -213,20 +210,6 @@ const (
// ErrOrphanBlockIsNotAllowed indicates that an orphan block was submitted with
// BFDisallowOrphans flag raised.
ErrOrphanBlockIsNotAllowed
// ErrViolatingBoundedMergeDepth indicates that a block is violating finality from
// its own point of view
ErrViolatingBoundedMergeDepth
// ErrViolatingMergeLimit indicates that a block merges more than mergeLimit blocks
ErrViolatingMergeLimit
// ErrChainedTransactions indicates that a block contains a transaction that spends an output of a transaction
// In the same block
ErrChainedTransactions
// ErrSelectedParentDisqualifiedFromChain indicates that a block's selectedParent has the status DisqualifiedFromChain
ErrSelectedParentDisqualifiedFromChain
)
// Map of ErrorCode values back to their constant names for pretty printing.

View File

@@ -13,15 +13,158 @@ import (
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/testtools"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/mining"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
)
// TestFinality checks that the finality mechanism works as expected.
// This is how the flow goes:
// 1) We build a chain of params.FinalityInterval blocks and call its tip altChainTip.
// 2) We build another chain (let's call it mainChain) of 2 * params.FinalityInterval
// blocks, which points to genesis, and then we check that the block in that
// chain with height of params.FinalityInterval is marked as finality point (This is
// very predictable, because the blue score of each new block in a chain is the
// parents plus one).
// 3) We make a new child to block with height (2 * params.FinalityInterval - 1)
// in mainChain, and we check that connecting it to the DAG
// doesn't affect the last finality point.
// 4) We make a block that points to genesis, and check that it
// gets rejected because its blue score is lower then the last finality
// point.
// 5) We make a block that points to altChainTip, and check that it
// gets rejected because it doesn't have the last finality point in
// its selected parent chain.
func TestFinality(t *testing.T) {
params := dagconfig.SimnetParams
params.K = 1
params.FinalityDuration = 100 * params.TargetTimePerBlock
dag, teardownFunc, err := blockdag.DAGSetup("TestFinality", true, blockdag.Config{
DAGParams: &params,
})
if err != nil {
t.Fatalf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
buildNodeToDag := func(parentHashes []*daghash.Hash) (*util.Block, error) {
msgBlock, err := mining.PrepareBlockForTest(dag, parentHashes, nil, false)
if err != nil {
return nil, err
}
block := util.NewBlock(msgBlock)
isOrphan, isDelayed, err := dag.ProcessBlock(block, blockdag.BFNoPoWCheck)
if err != nil {
return nil, err
}
if isDelayed {
return nil, errors.Errorf("ProcessBlock: block " +
"is too far in the future")
}
if isOrphan {
return nil, errors.Errorf("ProcessBlock: unexpected returned orphan block")
}
return block, nil
}
genesis := util.NewBlock(params.GenesisBlock)
currentNode := genesis
// First we build a chain of params.FinalityInterval blocks for future use
for i := uint64(0); i < dag.FinalityInterval(); i++ {
currentNode, err = buildNodeToDag([]*daghash.Hash{currentNode.Hash()})
if err != nil {
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
}
}
altChainTip := currentNode
// Now we build a new chain of 2 * params.FinalityInterval blocks, pointed to genesis, and
// we expect the block with height 1 * params.FinalityInterval to be the last finality point
currentNode = genesis
for i := uint64(0); i < dag.FinalityInterval(); i++ {
currentNode, err = buildNodeToDag([]*daghash.Hash{currentNode.Hash()})
if err != nil {
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
}
}
expectedFinalityPoint := currentNode
for i := uint64(0); i < dag.FinalityInterval(); i++ {
currentNode, err = buildNodeToDag([]*daghash.Hash{currentNode.Hash()})
if err != nil {
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
}
}
if !dag.LastFinalityPointHash().IsEqual(expectedFinalityPoint.Hash()) {
t.Errorf("TestFinality: dag.lastFinalityPoint expected to be %v but got %v", expectedFinalityPoint, dag.LastFinalityPointHash())
}
// Here we check that even if we create a parallel tip (a new tip with
// the same parents as the current one) with the same blue score as the
// current tip, it still won't affect the last finality point.
_, err = buildNodeToDag(currentNode.MsgBlock().Header.ParentHashes)
if err != nil {
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
}
if !dag.LastFinalityPointHash().IsEqual(expectedFinalityPoint.Hash()) {
t.Errorf("TestFinality: dag.lastFinalityPoint was unexpectly changed")
}
// Here we check that a block with lower blue score than the last finality
// point will get rejected
fakeCoinbaseTx, err := dag.NextBlockCoinbaseTransaction(nil, nil)
if err != nil {
t.Errorf("NextBlockCoinbaseTransaction: %s", err)
}
merkleRoot := blockdag.BuildHashMerkleTreeStore([]*util.Tx{fakeCoinbaseTx}).Root()
beforeFinalityBlock := domainmessage.NewMsgBlock(&domainmessage.BlockHeader{
Version: 0x10000000,
ParentHashes: []*daghash.Hash{genesis.Hash()},
HashMerkleRoot: merkleRoot,
AcceptedIDMerkleRoot: &daghash.ZeroHash,
UTXOCommitment: &daghash.ZeroHash,
Timestamp: dag.SelectedTipHeader().Timestamp,
Bits: genesis.MsgBlock().Header.Bits,
})
beforeFinalityBlock.AddTransaction(fakeCoinbaseTx.MsgTx())
_, _, err = dag.ProcessBlock(util.NewBlock(beforeFinalityBlock), blockdag.BFNoPoWCheck)
if err == nil {
t.Errorf("TestFinality: buildNodeToDag expected an error but got <nil>")
}
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)
}
// Here we check that a block that doesn't have the last finality point in
// its selected parent chain will get rejected
_, err = buildNodeToDag([]*daghash.Hash{altChainTip.Hash()})
if err == nil {
t.Errorf("TestFinality: buildNodeToDag expected an error but got <nil>")
}
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", ruleErr)
}
}
// TestFinalityInterval tests that the finality interval is
// smaller then appmessage.MaxInvPerMsg, so when a peer receives
// smaller then domainmessage.MaxInvPerMsg, so when a peer receives
// a getblocks message it should always be able to send
// all the necessary invs.
func TestFinalityInterval(t *testing.T) {
@@ -29,6 +172,7 @@ func TestFinalityInterval(t *testing.T) {
&dagconfig.MainnetParams,
&dagconfig.TestnetParams,
&dagconfig.DevnetParams,
&dagconfig.RegressionNetParams,
&dagconfig.SimnetParams,
}
for _, params := range netParams {
@@ -41,8 +185,8 @@ func TestFinalityInterval(t *testing.T) {
}
defer teardownFunc()
if dag.FinalityInterval() > appmessage.MaxInvPerMsg {
t.Errorf("FinalityInterval in %s should be lower or equal to appmessage.MaxInvPerMsg", params.Name)
if dag.FinalityInterval() > domainmessage.MaxInvPerMsg {
t.Errorf("FinalityInterval in %s should be lower or equal to domainmessage.MaxInvPerMsg", params.Name)
}
}()
}
@@ -88,7 +232,7 @@ func TestChainedTransactions(t *testing.T) {
}
defer teardownFunc()
block1, err := blockdag.PrepareBlockForTest(dag, []*daghash.Hash{params.GenesisHash}, nil)
block1, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{params.GenesisHash}, nil, false)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -109,34 +253,34 @@ func TestChainedTransactions(t *testing.T) {
if err != nil {
t.Fatalf("Failed to build signature script: %s", err)
}
txIn := &appmessage.TxIn{
PreviousOutpoint: appmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
txIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: appmessage.MaxTxInSequenceNum,
Sequence: domainmessage.MaxTxInSequenceNum,
}
txOut := &appmessage.TxOut{
txOut := &domainmessage.TxOut{
ScriptPubKey: blockdag.OpTrueScript,
Value: uint64(1),
}
tx := appmessage.NewNativeMsgTx(appmessage.TxVersion, []*appmessage.TxIn{txIn}, []*appmessage.TxOut{txOut})
tx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut})
chainedTxIn := &appmessage.TxIn{
PreviousOutpoint: appmessage.Outpoint{TxID: *tx.TxID(), Index: 0},
chainedTxIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *tx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: appmessage.MaxTxInSequenceNum,
Sequence: domainmessage.MaxTxInSequenceNum,
}
scriptPubKey, err := txscript.PayToScriptHashScript(blockdag.OpTrueScript)
if err != nil {
t.Fatalf("Failed to build public key script: %s", err)
}
chainedTxOut := &appmessage.TxOut{
chainedTxOut := &domainmessage.TxOut{
ScriptPubKey: scriptPubKey,
Value: uint64(1),
}
chainedTx := appmessage.NewNativeMsgTx(appmessage.TxVersion, []*appmessage.TxIn{chainedTxIn}, []*appmessage.TxOut{chainedTxOut})
chainedTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{chainedTxIn}, []*domainmessage.TxOut{chainedTxOut})
block2, err := blockdag.PrepareBlockForTest(dag, []*daghash.Hash{block1.BlockHash()}, []*appmessage.MsgTx{tx})
block2, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{block1.BlockHash()}, []*domainmessage.MsgTx{tx}, false)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -156,8 +300,8 @@ func TestChainedTransactions(t *testing.T) {
} else {
var ruleErr blockdag.RuleError
if ok := errors.As(err, &ruleErr); ok {
if ruleErr.ErrorCode != blockdag.ErrChainedTransactions {
t.Errorf("ProcessBlock expected an %v error code but got %v", blockdag.ErrChainedTransactions, ruleErr.ErrorCode)
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)
@@ -171,18 +315,18 @@ func TestChainedTransactions(t *testing.T) {
t.Errorf("ProcessBlock: block2 got unexpectedly orphaned")
}
nonChainedTxIn := &appmessage.TxIn{
PreviousOutpoint: appmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
nonChainedTxIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: appmessage.MaxTxInSequenceNum,
Sequence: domainmessage.MaxTxInSequenceNum,
}
nonChainedTxOut := &appmessage.TxOut{
nonChainedTxOut := &domainmessage.TxOut{
ScriptPubKey: scriptPubKey,
Value: uint64(1),
}
nonChainedTx := appmessage.NewNativeMsgTx(appmessage.TxVersion, []*appmessage.TxIn{nonChainedTxIn}, []*appmessage.TxOut{nonChainedTxOut})
nonChainedTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{nonChainedTxIn}, []*domainmessage.TxOut{nonChainedTxOut})
block3, err := blockdag.PrepareBlockForTest(dag, []*daghash.Hash{block1.BlockHash()}, []*appmessage.MsgTx{nonChainedTx})
block3, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{block1.BlockHash()}, []*domainmessage.MsgTx{nonChainedTx}, false)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -219,29 +363,29 @@ func TestOrderInDiffFromAcceptanceData(t *testing.T) {
createBlock := func(previousBlock *util.Block) *util.Block {
// Prepare a transaction that spends the previous block's coinbase transaction
var txs []*appmessage.MsgTx
var txs []*domainmessage.MsgTx
if !previousBlock.IsGenesis() {
previousCoinbaseTx := previousBlock.MsgBlock().Transactions[0]
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
if err != nil {
t.Fatalf("TestOrderInDiffFromAcceptanceData: Failed to build signature script: %s", err)
}
txIn := &appmessage.TxIn{
PreviousOutpoint: appmessage.Outpoint{TxID: *previousCoinbaseTx.TxID(), Index: 0},
txIn := &domainmessage.TxIn{
PreviousOutpoint: domainmessage.Outpoint{TxID: *previousCoinbaseTx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: appmessage.MaxTxInSequenceNum,
Sequence: domainmessage.MaxTxInSequenceNum,
}
txOut := &appmessage.TxOut{
txOut := &domainmessage.TxOut{
ScriptPubKey: blockdag.OpTrueScript,
Value: uint64(1),
}
txs = append(txs, appmessage.NewNativeMsgTx(appmessage.TxVersion, []*appmessage.TxIn{txIn}, []*appmessage.TxOut{txOut}))
txs = append(txs, domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut}))
}
// Create the block
msgBlock, err := blockdag.PrepareBlockForTest(dag, []*daghash.Hash{previousBlock.Hash()}, txs)
msgBlock, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{previousBlock.Hash()}, txs, false)
if err != nil {
t.Fatalf("TestOrderInDiffFromAcceptanceData: Failed to prepare block: %+v", err)
t.Fatalf("TestOrderInDiffFromAcceptanceData: Failed to prepare block: %s", err)
}
// Add the block to the DAG
@@ -294,9 +438,9 @@ func TestGasLimit(t *testing.T) {
t.Fatalf("could not register network: %s", err)
}
cbTxs := []*appmessage.MsgTx{}
cbTxs := []*domainmessage.MsgTx{}
for i := 0; i < 4; i++ {
fundsBlock, err := blockdag.PrepareBlockForTest(dag, dag.VirtualParentHashes(), nil)
fundsBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), nil, false)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -325,30 +469,30 @@ func TestGasLimit(t *testing.T) {
t.Fatalf("Failed to build public key script: %s", err)
}
tx1In := &appmessage.TxIn{
PreviousOutpoint: *appmessage.NewOutpoint(cbTxs[0].TxID(), 0),
Sequence: appmessage.MaxTxInSequenceNum,
tx1In := &domainmessage.TxIn{
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[0].TxID(), 0),
Sequence: domainmessage.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
tx1Out := &appmessage.TxOut{
tx1Out := &domainmessage.TxOut{
Value: cbTxs[0].TxOut[0].Value,
ScriptPubKey: scriptPubKey,
}
tx1 := appmessage.NewSubnetworkMsgTx(appmessage.TxVersion, []*appmessage.TxIn{tx1In}, []*appmessage.TxOut{tx1Out}, subnetworkID, 10000, []byte{})
tx1 := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{tx1In}, []*domainmessage.TxOut{tx1Out}, subnetworkID, 10000, []byte{})
tx2In := &appmessage.TxIn{
PreviousOutpoint: *appmessage.NewOutpoint(cbTxs[1].TxID(), 0),
Sequence: appmessage.MaxTxInSequenceNum,
tx2In := &domainmessage.TxIn{
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[1].TxID(), 0),
Sequence: domainmessage.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
tx2Out := &appmessage.TxOut{
tx2Out := &domainmessage.TxOut{
Value: cbTxs[1].TxOut[0].Value,
ScriptPubKey: scriptPubKey,
}
tx2 := appmessage.NewSubnetworkMsgTx(appmessage.TxVersion, []*appmessage.TxIn{tx2In}, []*appmessage.TxOut{tx2Out}, subnetworkID, 10000, []byte{})
tx2 := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{tx2In}, []*domainmessage.TxOut{tx2Out}, subnetworkID, 10000, []byte{})
// Here we check that we can't process a block that has transactions that exceed the gas limit
overLimitBlock, err := blockdag.PrepareBlockForTest(dag, dag.VirtualParentHashes(), []*appmessage.MsgTx{tx1, tx2})
overLimitBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{tx1, tx2}, true)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -370,20 +514,20 @@ func TestGasLimit(t *testing.T) {
t.Fatalf("ProcessBlock: overLimitBlock got unexpectedly orphan")
}
overflowGasTxIn := &appmessage.TxIn{
PreviousOutpoint: *appmessage.NewOutpoint(cbTxs[2].TxID(), 0),
Sequence: appmessage.MaxTxInSequenceNum,
overflowGasTxIn := &domainmessage.TxIn{
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[2].TxID(), 0),
Sequence: domainmessage.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
overflowGasTxOut := &appmessage.TxOut{
overflowGasTxOut := &domainmessage.TxOut{
Value: cbTxs[2].TxOut[0].Value,
ScriptPubKey: scriptPubKey,
}
overflowGasTx := appmessage.NewSubnetworkMsgTx(appmessage.TxVersion, []*appmessage.TxIn{overflowGasTxIn}, []*appmessage.TxOut{overflowGasTxOut},
overflowGasTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{overflowGasTxIn}, []*domainmessage.TxOut{overflowGasTxOut},
subnetworkID, math.MaxUint64, []byte{})
// Here we check that we can't process a block that its transactions' gas overflows uint64
overflowGasBlock, err := blockdag.PrepareBlockForTest(dag, dag.VirtualParentHashes(), []*appmessage.MsgTx{tx1, overflowGasTx})
overflowGasBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{tx1, overflowGasTx}, true)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -405,19 +549,19 @@ func TestGasLimit(t *testing.T) {
}
nonExistentSubnetwork := &subnetworkid.SubnetworkID{123}
nonExistentSubnetworkTxIn := &appmessage.TxIn{
PreviousOutpoint: *appmessage.NewOutpoint(cbTxs[3].TxID(), 0),
Sequence: appmessage.MaxTxInSequenceNum,
nonExistentSubnetworkTxIn := &domainmessage.TxIn{
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[3].TxID(), 0),
Sequence: domainmessage.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
nonExistentSubnetworkTxOut := &appmessage.TxOut{
nonExistentSubnetworkTxOut := &domainmessage.TxOut{
Value: cbTxs[3].TxOut[0].Value,
ScriptPubKey: scriptPubKey,
}
nonExistentSubnetworkTx := appmessage.NewSubnetworkMsgTx(appmessage.TxVersion, []*appmessage.TxIn{nonExistentSubnetworkTxIn},
[]*appmessage.TxOut{nonExistentSubnetworkTxOut}, nonExistentSubnetwork, 1, []byte{})
nonExistentSubnetworkTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{nonExistentSubnetworkTxIn},
[]*domainmessage.TxOut{nonExistentSubnetworkTxOut}, nonExistentSubnetwork, 1, []byte{})
nonExistentSubnetworkBlock, err := blockdag.PrepareBlockForTest(dag, dag.VirtualParentHashes(), []*appmessage.MsgTx{nonExistentSubnetworkTx, overflowGasTx})
nonExistentSubnetworkBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{nonExistentSubnetworkTx, overflowGasTx}, true)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
@@ -438,7 +582,7 @@ func TestGasLimit(t *testing.T) {
}
// Here we check that we can process a block with a transaction that doesn't exceed the gas limit
validBlock, err := blockdag.PrepareBlockForTest(dag, dag.VirtualParentHashes(), []*appmessage.MsgTx{tx1})
validBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{tx1}, true)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}

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