mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-23 11:58:22 +00:00
Compare commits
3 Commits
v0.8.1-dev
...
v0.7.3-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaf9117225 | ||
|
|
b88e34fd84 | ||
|
|
689098082f |
20
app/app.go
20
app/app.go
@@ -7,9 +7,9 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database/ldb"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
|
||||
|
||||
"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"
|
||||
@@ -46,7 +46,7 @@ func StartApp() error {
|
||||
// initializes logging and configures it accordingly.
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return err
|
||||
}
|
||||
defer panics.HandlePanic(log, "MAIN", nil)
|
||||
@@ -123,6 +123,16 @@ func (app *kaspadApp) main(startedChan chan<- struct{}) error {
|
||||
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 {
|
||||
@@ -178,8 +188,8 @@ func removeDatabase(cfg *config.Config) error {
|
||||
return os.RemoveAll(dbPath)
|
||||
}
|
||||
|
||||
func openDB(cfg *config.Config) (database.Database, error) {
|
||||
func openDB(cfg *config.Config) (*dbaccess.DatabaseContext, error) {
|
||||
dbPath := databasePath(cfg)
|
||||
log.Infof("Loading database from '%s'", dbPath)
|
||||
return ldb.NewLevelDB(dbPath)
|
||||
return dbaccess.New(dbPath)
|
||||
}
|
||||
|
||||
431
app/appmessage/bench_test.go
Normal file
431
app/appmessage/bench_test.go
Normal file
@@ -0,0 +1,431 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// genesisCoinbaseTx is the coinbase transaction for the genesis blocks for
|
||||
// the main network and test network.
|
||||
var genesisCoinbaseTxIns = []*TxIn{
|
||||
{
|
||||
PreviousOutpoint: Outpoint{
|
||||
TxID: daghash.TxID{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
SignatureScript: []byte{
|
||||
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, 0x45, /* |.......E| */
|
||||
0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, /* |The Time| */
|
||||
0x73, 0x20, 0x30, 0x33, 0x2f, 0x4a, 0x61, 0x6e, /* |s 03/Jan| */
|
||||
0x2f, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68, /* |/2009 Ch| */
|
||||
0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x6f, 0x72, /* |ancellor| */
|
||||
0x20, 0x6f, 0x6e, 0x20, 0x62, 0x72, 0x69, 0x6e, /* | on brin| */
|
||||
0x6b, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x63, /* |k of sec|*/
|
||||
0x6f, 0x6e, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6c, /* |ond bail| */
|
||||
0x6f, 0x75, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, /* |out for |*/
|
||||
0x62, 0x61, 0x6e, 0x6b, 0x73, /* |banks| */
|
||||
},
|
||||
Sequence: math.MaxUint64,
|
||||
},
|
||||
}
|
||||
var genesisCoinbaseTxOuts = []*TxOut{
|
||||
{
|
||||
Value: 0x12a05f200,
|
||||
ScriptPubKey: []byte{
|
||||
0x41, 0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, /* |A.g....U| */
|
||||
0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, /* |H'.g..q0| */
|
||||
0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, /* |..\..(.9| */
|
||||
0x09, 0xa6, 0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, /* |..yb...a| */
|
||||
0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, /* |..I..?L.| */
|
||||
0x38, 0xc4, 0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, /* |8..U....| */
|
||||
0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, /* |..\8M...| */
|
||||
0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, /* |.W.Lp+k.| */
|
||||
0x1d, 0x5f, 0xac, /* |._.| */
|
||||
},
|
||||
},
|
||||
}
|
||||
var genesisCoinbaseTx = NewNativeMsgTx(1, genesisCoinbaseTxIns, genesisCoinbaseTxOuts)
|
||||
|
||||
// BenchmarkWriteVarInt1 performs a benchmark on how long it takes to write
|
||||
// a single byte variable length integer.
|
||||
func BenchmarkWriteVarInt1(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarInt(ioutil.Discard, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarInt3 performs a benchmark on how long it takes to write
|
||||
// a three byte variable length integer.
|
||||
func BenchmarkWriteVarInt3(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarInt(ioutil.Discard, 65535)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarInt5 performs a benchmark on how long it takes to write
|
||||
// a five byte variable length integer.
|
||||
func BenchmarkWriteVarInt5(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarInt(ioutil.Discard, 4294967295)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarInt9 performs a benchmark on how long it takes to write
|
||||
// a nine byte variable length integer.
|
||||
func BenchmarkWriteVarInt9(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarInt(ioutil.Discard, 18446744073709551615)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarInt1 performs a benchmark on how long it takes to read
|
||||
// a single byte variable length integer.
|
||||
func BenchmarkReadVarInt1(b *testing.B) {
|
||||
buf := []byte{0x01}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarInt(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarInt3 performs a benchmark on how long it takes to read
|
||||
// a three byte variable length integer.
|
||||
func BenchmarkReadVarInt3(b *testing.B) {
|
||||
buf := []byte{0x0fd, 0xff, 0xff}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarInt(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarInt5 performs a benchmark on how long it takes to read
|
||||
// a five byte variable length integer.
|
||||
func BenchmarkReadVarInt5(b *testing.B) {
|
||||
buf := []byte{0xfe, 0xff, 0xff, 0xff, 0xff}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarInt(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarInt9 performs a benchmark on how long it takes to read
|
||||
// a nine byte variable length integer.
|
||||
func BenchmarkReadVarInt9(b *testing.B) {
|
||||
buf := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarInt(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarStr4 performs a benchmark on how long it takes to read a
|
||||
// four byte variable length string.
|
||||
func BenchmarkReadVarStr4(b *testing.B) {
|
||||
buf := []byte{0x04, 't', 'e', 's', 't'}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarString(r, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarStr10 performs a benchmark on how long it takes to read a
|
||||
// ten byte variable length string.
|
||||
func BenchmarkReadVarStr10(b *testing.B) {
|
||||
buf := []byte{0x0a, 't', 'e', 's', 't', '0', '1', '2', '3', '4', '5'}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarString(r, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarStr4 performs a benchmark on how long it takes to write a
|
||||
// four byte variable length string.
|
||||
func BenchmarkWriteVarStr4(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarString(ioutil.Discard, "test")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarStr10 performs a benchmark on how long it takes to write a
|
||||
// ten byte variable length string.
|
||||
func BenchmarkWriteVarStr10(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarString(ioutil.Discard, "test012345")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadOutpoint performs a benchmark on how long it takes to read a
|
||||
// transaction outpoint.
|
||||
func BenchmarkReadOutpoint(b *testing.B) {
|
||||
buf := []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Previous output index
|
||||
}
|
||||
r := bytes.NewReader(buf)
|
||||
var op Outpoint
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
readOutpoint(r, 0, 0, &op)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteOutpoint performs a benchmark on how long it takes to write a
|
||||
// transaction outpoint.
|
||||
func BenchmarkWriteOutpoint(b *testing.B) {
|
||||
op := &Outpoint{
|
||||
TxID: daghash.TxID{},
|
||||
Index: 0,
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
writeOutpoint(ioutil.Discard, 0, 0, op)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadTxOut performs a benchmark on how long it takes to read a
|
||||
// transaction output.
|
||||
func BenchmarkReadTxOut(b *testing.B) {
|
||||
buf := []byte{
|
||||
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0x43, // Varint for length of scriptPubKey
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||
0xee, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
}
|
||||
r := bytes.NewReader(buf)
|
||||
var txOut TxOut
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
readTxOut(r, 0, 0, &txOut)
|
||||
scriptPool.Return(txOut.ScriptPubKey)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteTxOut performs a benchmark on how long it takes to write
|
||||
// a transaction output.
|
||||
func BenchmarkWriteTxOut(b *testing.B) {
|
||||
txOut := blockOne.Transactions[0].TxOut[0]
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteTxOut(ioutil.Discard, 0, 0, txOut)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadTxIn performs a benchmark on how long it takes to read a
|
||||
// transaction input.
|
||||
func BenchmarkReadTxIn(b *testing.B) {
|
||||
buf := []byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Previous output index
|
||||
0x07, // Varint for length of signature script
|
||||
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence
|
||||
}
|
||||
r := bytes.NewReader(buf)
|
||||
var txIn TxIn
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
readTxIn(r, 0, 0, &txIn)
|
||||
scriptPool.Return(txIn.SignatureScript)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteTxIn performs a benchmark on how long it takes to write
|
||||
// a transaction input.
|
||||
func BenchmarkWriteTxIn(b *testing.B) {
|
||||
txIn := blockOne.Transactions[0].TxIn[0]
|
||||
for i := 0; i < b.N; i++ {
|
||||
writeTxIn(ioutil.Discard, 0, 0, txIn, txEncodingFull)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDeserializeTx performs a benchmark on how long it takes to
|
||||
// deserialize a small transaction.
|
||||
func BenchmarkDeserializeTxSmall(b *testing.B) {
|
||||
buf := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x01, // Varint for number of input transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||
0x07, // Varint for length of signature script
|
||||
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence
|
||||
0x01, // Varint for number of output transactions
|
||||
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0x43, // Varint for length of scriptPubKey
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||
0xee, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
}
|
||||
|
||||
r := bytes.NewReader(buf)
|
||||
var tx MsgTx
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
tx.Deserialize(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDeserializeTxLarge performs a benchmark on how long it takes to
|
||||
// deserialize a very large transaction.
|
||||
func BenchmarkDeserializeTxLarge(b *testing.B) {
|
||||
fi, err := os.Open("testdata/megatx.bin.bz2")
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to read transaction data: %v", err)
|
||||
}
|
||||
defer fi.Close()
|
||||
buf, err := ioutil.ReadAll(bzip2.NewReader(fi))
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to read transaction data: %v", err)
|
||||
}
|
||||
|
||||
r := bytes.NewReader(buf)
|
||||
var tx MsgTx
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
tx.Deserialize(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSerializeTx performs a benchmark on how long it takes to serialize
|
||||
// a transaction.
|
||||
func BenchmarkSerializeTx(b *testing.B) {
|
||||
tx := blockOne.Transactions[0]
|
||||
for i := 0; i < b.N; i++ {
|
||||
tx.Serialize(ioutil.Discard)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadBlockHeader performs a benchmark on how long it takes to
|
||||
// deserialize a block header.
|
||||
func BenchmarkReadBlockHeader(b *testing.B) {
|
||||
buf := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
||||
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // MerkleRoot
|
||||
0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, // Timestamp
|
||||
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Fake Nonce
|
||||
0x00, // TxnCount Varint
|
||||
}
|
||||
r := bytes.NewReader(buf)
|
||||
var header BlockHeader
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
readBlockHeader(r, 0, &header)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteBlockHeader performs a benchmark on how long it takes to
|
||||
// serialize a block header.
|
||||
func BenchmarkWriteBlockHeader(b *testing.B) {
|
||||
header := blockOne.Header
|
||||
for i := 0; i < b.N; i++ {
|
||||
writeBlockHeader(ioutil.Discard, 0, &header)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkTxHash performs a benchmark on how long it takes to hash a
|
||||
// transaction.
|
||||
func BenchmarkTxHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
genesisCoinbaseTx.TxHash()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDoubleHashB performs a benchmark on how long it takes to perform a
|
||||
// double hash returning a byte slice.
|
||||
func BenchmarkDoubleHashB(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
if err := genesisCoinbaseTx.Serialize(&buf); err != nil {
|
||||
b.Errorf("Serialize: unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
txBytes := buf.Bytes()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = daghash.DoubleHashB(txBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDoubleHashH performs a benchmark on how long it takes to perform
|
||||
// a double hash returning a daghash.Hash.
|
||||
func BenchmarkDoubleHashH(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
if err := genesisCoinbaseTx.Serialize(&buf); err != nil {
|
||||
b.Errorf("Serialize: unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
txBytes := buf.Bytes()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = daghash.DoubleHashH(txBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDoubleHashWriter performs a benchmark on how long it takes to perform
|
||||
// a double hash via the writer returning a daghash.Hash.
|
||||
func BenchmarkDoubleHashWriter(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
err := genesisCoinbaseTx.Serialize(&buf)
|
||||
if err != nil {
|
||||
b.Fatalf("Serialize: unexpected error: %+v", err)
|
||||
}
|
||||
txBytes := buf.Bytes()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
writer := daghash.NewDoubleHashWriter()
|
||||
_, _ = writer.Write(txBytes)
|
||||
writer.Finalize()
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,14 @@ package appmessage
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/binaryserializer"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
// MaxVarIntPayload is the maximum payload size for a variable length integer.
|
||||
@@ -138,7 +138,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
}
|
||||
return nil
|
||||
|
||||
case *externalapi.DomainHash:
|
||||
case *daghash.Hash:
|
||||
_, err := io.ReadFull(r, e[:])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -148,7 +148,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
case *id.ID:
|
||||
return e.Deserialize(r)
|
||||
|
||||
case *externalapi.DomainSubnetworkID:
|
||||
case *subnetworkid.SubnetworkID:
|
||||
_, err := io.ReadFull(r, e[:])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -263,7 +263,7 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
}
|
||||
return nil
|
||||
|
||||
case *externalapi.DomainHash:
|
||||
case *daghash.Hash:
|
||||
_, err := w.Write(e[:])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -273,7 +273,7 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
case *id.ID:
|
||||
return e.Serialize(w)
|
||||
|
||||
case *externalapi.DomainSubnetworkID:
|
||||
case *subnetworkid.SubnetworkID:
|
||||
_, err := w.Write(e[:])
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -6,20 +6,19 @@ package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// mainnetGenesisHash is the hash of the first block in the block DAG for the
|
||||
// main network (genesis block).
|
||||
var mainnetGenesisHash = &externalapi.DomainHash{
|
||||
var mainnetGenesisHash = &daghash.Hash{
|
||||
0xdc, 0x5f, 0x5b, 0x5b, 0x1d, 0xc2, 0xa7, 0x25,
|
||||
0x49, 0xd5, 0x1d, 0x4d, 0xee, 0xd7, 0xa4, 0x8b,
|
||||
0xaf, 0xd3, 0x14, 0x4b, 0x56, 0x78, 0x98, 0xb1,
|
||||
@@ -28,30 +27,30 @@ var mainnetGenesisHash = &externalapi.DomainHash{
|
||||
|
||||
// simnetGenesisHash is the hash of the first block in the block DAG for the
|
||||
// simulation test network.
|
||||
var simnetGenesisHash = &externalapi.DomainHash{
|
||||
0x9d, 0x89, 0xb0, 0x6e, 0xb3, 0x47, 0xb5, 0x6e,
|
||||
0xcd, 0x6c, 0x63, 0x99, 0x45, 0x91, 0xd5, 0xce,
|
||||
0x9b, 0x43, 0x05, 0xc1, 0xa5, 0x5e, 0x2a, 0xda,
|
||||
0x90, 0x4c, 0xf0, 0x6c, 0x4d, 0x5f, 0xd3, 0x62,
|
||||
var simnetGenesisHash = &daghash.Hash{
|
||||
0xf6, 0x7a, 0xd7, 0x69, 0x5d, 0x9b, 0x66, 0x2a,
|
||||
0x72, 0xff, 0x3d, 0x8e, 0xdb, 0xbb, 0x2d, 0xe0,
|
||||
0xbf, 0xa6, 0x7b, 0x13, 0x97, 0x4b, 0xb9, 0x91,
|
||||
0x0d, 0x11, 0x6d, 0x5c, 0xbd, 0x86, 0x3e, 0x68,
|
||||
}
|
||||
|
||||
// mainnetGenesisMerkleRoot is the hash of the first transaction in the genesis
|
||||
// block for the main network.
|
||||
var mainnetGenesisMerkleRoot = &externalapi.DomainHash{
|
||||
var mainnetGenesisMerkleRoot = &daghash.Hash{
|
||||
0x4a, 0x5e, 0x1e, 0x4b, 0xaa, 0xb8, 0x9f, 0x3a,
|
||||
0x32, 0x51, 0x8a, 0x88, 0xc3, 0x1b, 0xc8, 0x7f,
|
||||
0x61, 0x8f, 0x76, 0x67, 0x3e, 0x2c, 0xc7, 0x7a,
|
||||
0xb2, 0x12, 0x7b, 0x7a, 0xfd, 0xed, 0xa3, 0x3b,
|
||||
}
|
||||
|
||||
var exampleAcceptedIDMerkleRoot = &externalapi.DomainHash{
|
||||
var exampleAcceptedIDMerkleRoot = &daghash.Hash{
|
||||
0x09, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C,
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
}
|
||||
|
||||
var exampleUTXOCommitment = &externalapi.DomainHash{
|
||||
var exampleUTXOCommitment = &daghash.Hash{
|
||||
0x10, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C,
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
@@ -105,7 +104,7 @@ func TestElementEncoding(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
(*externalapi.DomainHash)(&[externalapi.DomainHashSize]byte{
|
||||
(*daghash.Hash)(&[daghash.HashSize]byte{
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
@@ -194,7 +193,7 @@ func TestElementEncodingErrors(t *testing.T) {
|
||||
0, io.ErrShortWrite, io.EOF,
|
||||
},
|
||||
{
|
||||
(*externalapi.DomainHash)(&[externalapi.DomainHashSize]byte{
|
||||
(*daghash.Hash)(&[daghash.HashSize]byte{
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
)
|
||||
|
||||
// DomainBlockToMsgBlock converts an externalapi.DomainBlock to MsgBlock
|
||||
func DomainBlockToMsgBlock(domainBlock *externalapi.DomainBlock) *MsgBlock {
|
||||
msgTxs := make([]*MsgTx, 0, len(domainBlock.Transactions))
|
||||
for _, domainTransaction := range domainBlock.Transactions {
|
||||
msgTxs = append(msgTxs, DomainTransactionToMsgTx(domainTransaction))
|
||||
}
|
||||
return &MsgBlock{
|
||||
Header: *DomainBlockHeaderToBlockHeader(domainBlock.Header),
|
||||
Transactions: msgTxs,
|
||||
}
|
||||
}
|
||||
|
||||
// DomainBlockHeaderToBlockHeader converts an externalapi.DomainBlockHeader to MsgBlockHeader
|
||||
func DomainBlockHeaderToBlockHeader(domainBlockHeader *externalapi.DomainBlockHeader) *MsgBlockHeader {
|
||||
return &MsgBlockHeader{
|
||||
Version: domainBlockHeader.Version,
|
||||
ParentHashes: domainBlockHeader.ParentHashes,
|
||||
HashMerkleRoot: &domainBlockHeader.HashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: &domainBlockHeader.AcceptedIDMerkleRoot,
|
||||
UTXOCommitment: &domainBlockHeader.UTXOCommitment,
|
||||
Timestamp: mstime.UnixMilliseconds(domainBlockHeader.TimeInMilliseconds),
|
||||
Bits: domainBlockHeader.Bits,
|
||||
Nonce: domainBlockHeader.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
// MsgBlockToDomainBlock converts a MsgBlock to externalapi.DomainBlock
|
||||
func MsgBlockToDomainBlock(msgBlock *MsgBlock) *externalapi.DomainBlock {
|
||||
transactions := make([]*externalapi.DomainTransaction, 0, len(msgBlock.Transactions))
|
||||
for _, msgTx := range msgBlock.Transactions {
|
||||
transactions = append(transactions, MsgTxToDomainTransaction(msgTx))
|
||||
}
|
||||
|
||||
return &externalapi.DomainBlock{
|
||||
Header: BlockHeaderToDomainBlockHeader(&msgBlock.Header),
|
||||
Transactions: transactions,
|
||||
}
|
||||
}
|
||||
|
||||
// BlockHeaderToDomainBlockHeader converts a MsgBlockHeader to externalapi.DomainBlockHeader
|
||||
func BlockHeaderToDomainBlockHeader(blockHeader *MsgBlockHeader) *externalapi.DomainBlockHeader {
|
||||
return &externalapi.DomainBlockHeader{
|
||||
Version: blockHeader.Version,
|
||||
ParentHashes: blockHeader.ParentHashes,
|
||||
HashMerkleRoot: *blockHeader.HashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: *blockHeader.AcceptedIDMerkleRoot,
|
||||
UTXOCommitment: *blockHeader.UTXOCommitment,
|
||||
TimeInMilliseconds: blockHeader.Timestamp.UnixMilliseconds(),
|
||||
Bits: blockHeader.Bits,
|
||||
Nonce: blockHeader.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
// DomainTransactionToMsgTx converts an externalapi.DomainTransaction into an MsgTx
|
||||
func DomainTransactionToMsgTx(domainTransaction *externalapi.DomainTransaction) *MsgTx {
|
||||
txIns := make([]*TxIn, 0, len(domainTransaction.Inputs))
|
||||
for _, input := range domainTransaction.Inputs {
|
||||
txIns = append(txIns, domainTransactionInputToTxIn(input))
|
||||
}
|
||||
|
||||
txOuts := make([]*TxOut, 0, len(domainTransaction.Outputs))
|
||||
for _, output := range domainTransaction.Outputs {
|
||||
txOuts = append(txOuts, domainTransactionOutputToTxOut(output))
|
||||
}
|
||||
|
||||
return &MsgTx{
|
||||
Version: domainTransaction.Version,
|
||||
TxIn: txIns,
|
||||
TxOut: txOuts,
|
||||
LockTime: domainTransaction.LockTime,
|
||||
SubnetworkID: domainTransaction.SubnetworkID,
|
||||
Gas: domainTransaction.Gas,
|
||||
PayloadHash: domainTransaction.PayloadHash,
|
||||
Payload: domainTransaction.Payload,
|
||||
}
|
||||
}
|
||||
|
||||
func domainTransactionOutputToTxOut(domainTransactionOutput *externalapi.DomainTransactionOutput) *TxOut {
|
||||
return &TxOut{
|
||||
Value: domainTransactionOutput.Value,
|
||||
ScriptPubKey: domainTransactionOutput.ScriptPublicKey,
|
||||
}
|
||||
}
|
||||
|
||||
func domainTransactionInputToTxIn(domainTransactionInput *externalapi.DomainTransactionInput) *TxIn {
|
||||
return &TxIn{
|
||||
PreviousOutpoint: *domainOutpointToOutpoint(domainTransactionInput.PreviousOutpoint),
|
||||
SignatureScript: domainTransactionInput.SignatureScript,
|
||||
Sequence: domainTransactionInput.Sequence,
|
||||
}
|
||||
}
|
||||
|
||||
func domainOutpointToOutpoint(domainOutpoint externalapi.DomainOutpoint) *Outpoint {
|
||||
return NewOutpoint(
|
||||
&domainOutpoint.TransactionID,
|
||||
domainOutpoint.Index)
|
||||
}
|
||||
|
||||
// MsgTxToDomainTransaction converts an MsgTx into externalapi.DomainTransaction
|
||||
func MsgTxToDomainTransaction(msgTx *MsgTx) *externalapi.DomainTransaction {
|
||||
transactionInputs := make([]*externalapi.DomainTransactionInput, 0, len(msgTx.TxIn))
|
||||
for _, txIn := range msgTx.TxIn {
|
||||
transactionInputs = append(transactionInputs, txInToDomainTransactionInput(txIn))
|
||||
}
|
||||
|
||||
transactionOutputs := make([]*externalapi.DomainTransactionOutput, 0, len(msgTx.TxOut))
|
||||
for _, txOut := range msgTx.TxOut {
|
||||
transactionOutputs = append(transactionOutputs, txOutToDomainTransactionOutput(txOut))
|
||||
}
|
||||
|
||||
payload := make([]byte, 0)
|
||||
if msgTx.Payload != nil {
|
||||
payload = msgTx.Payload
|
||||
}
|
||||
|
||||
return &externalapi.DomainTransaction{
|
||||
Version: msgTx.Version,
|
||||
Inputs: transactionInputs,
|
||||
Outputs: transactionOutputs,
|
||||
LockTime: msgTx.LockTime,
|
||||
SubnetworkID: msgTx.SubnetworkID,
|
||||
Gas: msgTx.Gas,
|
||||
PayloadHash: msgTx.PayloadHash,
|
||||
Payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
func txOutToDomainTransactionOutput(txOut *TxOut) *externalapi.DomainTransactionOutput {
|
||||
return &externalapi.DomainTransactionOutput{
|
||||
Value: txOut.Value,
|
||||
ScriptPublicKey: txOut.ScriptPubKey,
|
||||
}
|
||||
}
|
||||
|
||||
func txInToDomainTransactionInput(txIn *TxIn) *externalapi.DomainTransactionInput {
|
||||
return &externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: *outpointToDomainOutpoint(&txIn.PreviousOutpoint), //TODO
|
||||
SignatureScript: txIn.SignatureScript,
|
||||
Sequence: txIn.Sequence,
|
||||
}
|
||||
}
|
||||
|
||||
func outpointToDomainOutpoint(outpoint *Outpoint) *externalapi.DomainOutpoint {
|
||||
return &externalapi.DomainOutpoint{
|
||||
TransactionID: outpoint.TxID,
|
||||
Index: outpoint.Index,
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ const (
|
||||
CmdVerAck
|
||||
CmdRequestAddresses
|
||||
CmdAddresses
|
||||
CmdRequestHeaders
|
||||
CmdRequestIBDBlocks
|
||||
CmdBlock
|
||||
CmdTx
|
||||
CmdPing
|
||||
@@ -48,15 +48,10 @@ const (
|
||||
CmdInvTransaction
|
||||
CmdRequestTransactions
|
||||
CmdIBDBlock
|
||||
CmdDoneHeaders
|
||||
CmdRequestNextIBDBlocks
|
||||
CmdDoneIBDBlocks
|
||||
CmdTransactionNotFound
|
||||
CmdReject
|
||||
CmdHeader
|
||||
CmdRequestNextHeaders
|
||||
CmdRequestIBDRootUTXOSetAndBlock
|
||||
CmdIBDRootUTXOSetAndBlock
|
||||
CmdRequestIBDBlocks
|
||||
CmdIBDRootNotFound
|
||||
|
||||
// rpc
|
||||
CmdGetCurrentNetworkRequestMessage
|
||||
@@ -112,33 +107,28 @@ const (
|
||||
|
||||
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
|
||||
var ProtocolMessageCommandToString = map[MessageCommand]string{
|
||||
CmdVersion: "Version",
|
||||
CmdVerAck: "VerAck",
|
||||
CmdRequestAddresses: "RequestAddresses",
|
||||
CmdAddresses: "Addresses",
|
||||
CmdRequestHeaders: "RequestHeaders",
|
||||
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",
|
||||
CmdDoneHeaders: "DoneHeaders",
|
||||
CmdTransactionNotFound: "TransactionNotFound",
|
||||
CmdReject: "Reject",
|
||||
CmdHeader: "Header",
|
||||
CmdRequestNextHeaders: "RequestNextHeaders",
|
||||
CmdRequestIBDRootUTXOSetAndBlock: "RequestPruningUTXOSetAndBlock",
|
||||
CmdIBDRootUTXOSetAndBlock: "IBDRootUTXOSetAndBlock",
|
||||
CmdRequestIBDBlocks: "RequestIBDBlocks",
|
||||
CmdIBDRootNotFound: "IBDRootNotFound",
|
||||
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
|
||||
|
||||
195
app/appmessage/p2p_blockheader.go
Normal file
195
app/appmessage/p2p_blockheader.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
// BaseBlockHeaderPayload is the base number of bytes a block header can be,
|
||||
// not including the list of parent block headers.
|
||||
// Version 4 bytes + Timestamp 8 bytes + Bits 4 bytes + Nonce 8 bytes +
|
||||
// + NumParentBlocks 1 byte + HashMerkleRoot hash +
|
||||
// + AcceptedIDMerkleRoot hash + UTXOCommitment hash.
|
||||
// To get total size of block header len(ParentHashes) * daghash.HashSize should be
|
||||
// added to this value
|
||||
const BaseBlockHeaderPayload = 25 + 3*(daghash.HashSize)
|
||||
|
||||
// MaxNumParentBlocks is the maximum number of parent blocks a block can reference.
|
||||
// Currently set to 255 as the maximum number NumParentBlocks can be due to it being a byte
|
||||
const MaxNumParentBlocks = 255
|
||||
|
||||
// MaxBlockHeaderPayload is the maximum number of bytes a block header can be.
|
||||
// BaseBlockHeaderPayload + up to MaxNumParentBlocks hashes of parent blocks
|
||||
const MaxBlockHeaderPayload = BaseBlockHeaderPayload + (MaxNumParentBlocks * daghash.HashSize)
|
||||
|
||||
// BlockHeader defines information about a block and is used in the kaspa
|
||||
// block (MsgBlock) and headers (MsgHeader) messages.
|
||||
type BlockHeader struct {
|
||||
// Version of the block. This is not the same as the protocol version.
|
||||
Version int32
|
||||
|
||||
// Hashes of the parent block headers in the blockDAG.
|
||||
ParentHashes []*daghash.Hash
|
||||
|
||||
// HashMerkleRoot is the merkle tree reference to hash of all transactions for the block.
|
||||
HashMerkleRoot *daghash.Hash
|
||||
|
||||
// AcceptedIDMerkleRoot is merkle tree reference to hash all transactions
|
||||
// accepted form the block.Blues
|
||||
AcceptedIDMerkleRoot *daghash.Hash
|
||||
|
||||
// UTXOCommitment is an ECMH UTXO commitment to the block UTXO.
|
||||
UTXOCommitment *daghash.Hash
|
||||
|
||||
// Time the block was created.
|
||||
Timestamp mstime.Time
|
||||
|
||||
// Difficulty target for the block.
|
||||
Bits uint32
|
||||
|
||||
// Nonce used to generate the block.
|
||||
Nonce uint64
|
||||
}
|
||||
|
||||
// NumParentBlocks return the number of entries in ParentHashes
|
||||
func (h *BlockHeader) NumParentBlocks() byte {
|
||||
numParents := len(h.ParentHashes)
|
||||
if numParents > math.MaxUint8 {
|
||||
panic(errors.Errorf("number of parents is %d, which is more than one byte can fit", numParents))
|
||||
}
|
||||
return byte(numParents)
|
||||
}
|
||||
|
||||
// BlockHash computes the block identifier hash for the given block header.
|
||||
func (h *BlockHeader) BlockHash() *daghash.Hash {
|
||||
// Encode the header and double sha256 everything prior to the number of
|
||||
// transactions.
|
||||
writer := daghash.NewDoubleHashWriter()
|
||||
err := writeBlockHeader(writer, 0, h)
|
||||
if err != nil {
|
||||
// It seems like this could only happen if the writer returned an error.
|
||||
// and this writer should never return an error (no allocations or possible failures)
|
||||
// the only non-writer error path here is unknown types in `WriteElement`
|
||||
panic(fmt.Sprintf("BlockHash() failed. this should never fail unless BlockHeader was changed. err: %+v", err))
|
||||
}
|
||||
|
||||
res := writer.Finalize()
|
||||
return &res
|
||||
}
|
||||
|
||||
// IsGenesis returns true iff this block is a genesis block
|
||||
func (h *BlockHeader) IsGenesis() bool {
|
||||
return h.NumParentBlocks() == 0
|
||||
}
|
||||
|
||||
// KaspaDecode decodes r using the kaspa protocol encoding into the receiver.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Deserialize for decoding block headers stored to disk, such as in a
|
||||
// database, as opposed to decoding block headers from the appmessage.
|
||||
func (h *BlockHeader) KaspaDecode(r io.Reader, pver uint32) error {
|
||||
return readBlockHeader(r, pver, h)
|
||||
}
|
||||
|
||||
// KaspaEncode encodes the receiver to w using the kaspa protocol encoding.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Serialize for encoding block headers to be stored to disk, such as in a
|
||||
// database, as opposed to encoding block headers for the appmessage.
|
||||
func (h *BlockHeader) KaspaEncode(w io.Writer, pver uint32) error {
|
||||
return writeBlockHeader(w, pver, h)
|
||||
}
|
||||
|
||||
// Deserialize decodes a block header from r into the receiver using a format
|
||||
// that is suitable for long-term storage such as a database while respecting
|
||||
// the Version field.
|
||||
func (h *BlockHeader) Deserialize(r io.Reader) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of readBlockHeader.
|
||||
return readBlockHeader(r, 0, h)
|
||||
}
|
||||
|
||||
// Serialize encodes a block header from r into the receiver using a format
|
||||
// that is suitable for long-term storage such as a database while respecting
|
||||
// the Version field.
|
||||
func (h *BlockHeader) Serialize(w io.Writer) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of writeBlockHeader.
|
||||
return writeBlockHeader(w, 0, h)
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize the
|
||||
// block header.
|
||||
func (h *BlockHeader) SerializeSize() int {
|
||||
return BaseBlockHeaderPayload + int(h.NumParentBlocks())*daghash.HashSize
|
||||
}
|
||||
|
||||
// NewBlockHeader returns a new BlockHeader using the provided version, previous
|
||||
// block hash, hash merkle root, accepted ID merkle root, difficulty bits, and nonce used to generate the
|
||||
// block with defaults or calclulated values for the remaining fields.
|
||||
func NewBlockHeader(version int32, parentHashes []*daghash.Hash, hashMerkleRoot *daghash.Hash,
|
||||
acceptedIDMerkleRoot *daghash.Hash, utxoCommitment *daghash.Hash, bits uint32, nonce uint64) *BlockHeader {
|
||||
|
||||
// Limit the timestamp to one millisecond precision since the protocol
|
||||
// doesn't support better.
|
||||
return &BlockHeader{
|
||||
Version: version,
|
||||
ParentHashes: parentHashes,
|
||||
HashMerkleRoot: hashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: acceptedIDMerkleRoot,
|
||||
UTXOCommitment: utxoCommitment,
|
||||
Timestamp: mstime.Now(),
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
}
|
||||
|
||||
// readBlockHeader reads a kaspa block header from r. See Deserialize for
|
||||
// decoding block headers stored to disk, such as in a database, as opposed to
|
||||
// decoding from the appmessage.
|
||||
func readBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error {
|
||||
var numParentBlocks byte
|
||||
err := readElements(r, &bh.Version, &numParentBlocks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bh.ParentHashes = make([]*daghash.Hash, numParentBlocks)
|
||||
for i := byte(0); i < numParentBlocks; i++ {
|
||||
hash := &daghash.Hash{}
|
||||
err := ReadElement(r, hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bh.ParentHashes[i] = hash
|
||||
}
|
||||
bh.HashMerkleRoot = &daghash.Hash{}
|
||||
bh.AcceptedIDMerkleRoot = &daghash.Hash{}
|
||||
bh.UTXOCommitment = &daghash.Hash{}
|
||||
return readElements(r, bh.HashMerkleRoot, bh.AcceptedIDMerkleRoot, bh.UTXOCommitment,
|
||||
(*int64Time)(&bh.Timestamp), &bh.Bits, &bh.Nonce)
|
||||
}
|
||||
|
||||
// writeBlockHeader writes a kaspa block header to w. See Serialize for
|
||||
// encoding block headers to be stored to disk, such as in a database, as
|
||||
// opposed to encoding for the appmessage.
|
||||
func writeBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error {
|
||||
timestamp := bh.Timestamp.UnixMilliseconds()
|
||||
if err := writeElements(w, bh.Version, bh.NumParentBlocks()); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, hash := range bh.ParentHashes {
|
||||
if err := WriteElement(w, hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return writeElements(w, bh.HashMerkleRoot, bh.AcceptedIDMerkleRoot, bh.UTXOCommitment, timestamp, bh.Bits, bh.Nonce)
|
||||
}
|
||||
345
app/appmessage/p2p_blockheader_test.go
Normal file
345
app/appmessage/p2p_blockheader_test.go
Normal file
@@ -0,0 +1,345 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/random"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestBlockHeader tests the BlockHeader API.
|
||||
func TestBlockHeader(t *testing.T) {
|
||||
nonce, err := random.Uint64()
|
||||
if err != nil {
|
||||
t.Errorf("random.Uint64: Error generating nonce: %v", err)
|
||||
}
|
||||
|
||||
hashes := []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash}
|
||||
|
||||
merkleHash := mainnetGenesisMerkleRoot
|
||||
acceptedIDMerkleRoot := exampleAcceptedIDMerkleRoot
|
||||
bits := uint32(0x1d00ffff)
|
||||
bh := NewBlockHeader(1, hashes, merkleHash, acceptedIDMerkleRoot, exampleUTXOCommitment, bits, nonce)
|
||||
|
||||
// Ensure we get the same data back out.
|
||||
if !reflect.DeepEqual(bh.ParentHashes, hashes) {
|
||||
t.Errorf("NewBlockHeader: wrong prev hashes - got %v, want %v",
|
||||
spew.Sprint(bh.ParentHashes), spew.Sprint(hashes))
|
||||
}
|
||||
if !bh.HashMerkleRoot.IsEqual(merkleHash) {
|
||||
t.Errorf("NewBlockHeader: wrong merkle root - got %v, want %v",
|
||||
spew.Sprint(bh.HashMerkleRoot), spew.Sprint(merkleHash))
|
||||
}
|
||||
if bh.Bits != bits {
|
||||
t.Errorf("NewBlockHeader: wrong bits - got %v, want %v",
|
||||
bh.Bits, bits)
|
||||
}
|
||||
if bh.Nonce != nonce {
|
||||
t.Errorf("NewBlockHeader: wrong nonce - got %v, want %v",
|
||||
bh.Nonce, nonce)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockHeaderEncoding tests the BlockHeader appmessage encode and decode for various
|
||||
// protocol versions.
|
||||
func TestBlockHeaderEncoding(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x000000000001e0f3
|
||||
pver := ProtocolVersion
|
||||
|
||||
// baseBlockHdr is used in the various tests as a baseline BlockHeader.
|
||||
bits := uint32(0x1d00ffff)
|
||||
baseBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: exampleAcceptedIDMerkleRoot,
|
||||
UTXOCommitment: exampleUTXOCommitment,
|
||||
Timestamp: mstime.UnixMilliseconds(0x17315ed0f99),
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
// baseBlockHdrEncoded is the appmessage encoded bytes of baseBlockHdr.
|
||||
baseBlockHdrEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||
0x02, // NumParentBlocks
|
||||
0xdc, 0x5f, 0x5b, 0x5b, 0x1d, 0xc2, 0xa7, 0x25, // mainnetGenesisHash
|
||||
0x49, 0xd5, 0x1d, 0x4d, 0xee, 0xd7, 0xa4, 0x8b,
|
||||
0xaf, 0xd3, 0x14, 0x4b, 0x56, 0x78, 0x98, 0xb1,
|
||||
0x8c, 0xfd, 0x9f, 0x69, 0xdd, 0xcf, 0xbb, 0x63,
|
||||
0xf6, 0x7a, 0xd7, 0x69, 0x5d, 0x9b, 0x66, 0x2a, // simnetGenesisHash
|
||||
0x72, 0xff, 0x3d, 0x8e, 0xdb, 0xbb, 0x2d, 0xe0,
|
||||
0xbf, 0xa6, 0x7b, 0x13, 0x97, 0x4b, 0xb9, 0x91,
|
||||
0x0d, 0x11, 0x6d, 0x5c, 0xbd, 0x86, 0x3e, 0x68,
|
||||
0x4a, 0x5e, 0x1e, 0x4b, 0xaa, 0xb8, 0x9f, 0x3a, // HashMerkleRoot
|
||||
0x32, 0x51, 0x8a, 0x88, 0xc3, 0x1b, 0xc8, 0x7f,
|
||||
0x61, 0x8f, 0x76, 0x67, 0x3e, 0x2c, 0xc7, 0x7a,
|
||||
0xb2, 0x12, 0x7b, 0x7a, 0xfd, 0xed, 0xa3, 0x3b,
|
||||
0x09, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // AcceptedIDMerkleRoot
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x10, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // UTXOCommitment
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x99, 0x0f, 0xed, 0x15, 0x73, 0x01, 0x00, 0x00, // Timestamp
|
||||
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Fake Nonce
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *BlockHeader // Data to encode
|
||||
out *BlockHeader // Expected decoded data
|
||||
buf []byte // Encoded data
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
{
|
||||
baseBlockHdr,
|
||||
baseBlockHdr,
|
||||
baseBlockHdrEncoded,
|
||||
ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := writeBlockHeader(&buf, test.pver, test.in)
|
||||
if err != nil {
|
||||
t.Errorf("writeBlockHeader #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("writeBlockHeader #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
err = test.in.KaspaEncode(&buf, pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the block header from appmessage format.
|
||||
var bh BlockHeader
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = readBlockHeader(rbuf, test.pver, &bh)
|
||||
if err != nil {
|
||||
t.Errorf("readBlockHeader #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&bh, test.out) {
|
||||
t.Errorf("readBlockHeader #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&bh), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
|
||||
rbuf = bytes.NewReader(test.buf)
|
||||
err = bh.KaspaDecode(rbuf, pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&bh, test.out) {
|
||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&bh), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockHeaderSerialize tests BlockHeader serialize and deserialize.
|
||||
func TestBlockHeaderSerialize(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x01e0f3
|
||||
|
||||
// baseBlockHdr is used in the various tests as a baseline BlockHeader.
|
||||
bits := uint32(0x1d00ffff)
|
||||
baseBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: exampleAcceptedIDMerkleRoot,
|
||||
UTXOCommitment: exampleUTXOCommitment,
|
||||
Timestamp: mstime.UnixMilliseconds(0x17315ed0f99),
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
// baseBlockHdrEncoded is the appmessage encoded bytes of baseBlockHdr.
|
||||
baseBlockHdrEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||
0x02, // NumParentBlocks
|
||||
0xdc, 0x5f, 0x5b, 0x5b, 0x1d, 0xc2, 0xa7, 0x25, // mainnetGenesisHash
|
||||
0x49, 0xd5, 0x1d, 0x4d, 0xee, 0xd7, 0xa4, 0x8b,
|
||||
0xaf, 0xd3, 0x14, 0x4b, 0x56, 0x78, 0x98, 0xb1,
|
||||
0x8c, 0xfd, 0x9f, 0x69, 0xdd, 0xcf, 0xbb, 0x63,
|
||||
0xf6, 0x7a, 0xd7, 0x69, 0x5d, 0x9b, 0x66, 0x2a, // simnetGenesisHash
|
||||
0x72, 0xff, 0x3d, 0x8e, 0xdb, 0xbb, 0x2d, 0xe0,
|
||||
0xbf, 0xa6, 0x7b, 0x13, 0x97, 0x4b, 0xb9, 0x91,
|
||||
0x0d, 0x11, 0x6d, 0x5c, 0xbd, 0x86, 0x3e, 0x68,
|
||||
0x4a, 0x5e, 0x1e, 0x4b, 0xaa, 0xb8, 0x9f, 0x3a, // HashMerkleRoot
|
||||
0x32, 0x51, 0x8a, 0x88, 0xc3, 0x1b, 0xc8, 0x7f,
|
||||
0x61, 0x8f, 0x76, 0x67, 0x3e, 0x2c, 0xc7, 0x7a,
|
||||
0xb2, 0x12, 0x7b, 0x7a, 0xfd, 0xed, 0xa3, 0x3b,
|
||||
0x09, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // AcceptedIDMerkleRoot
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x10, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // UTXOCommitment
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x99, 0x0f, 0xed, 0x15, 0x73, 0x01, 0x00, 0x00, // Timestamp
|
||||
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Fake Nonce
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *BlockHeader // Data to encode
|
||||
out *BlockHeader // Expected decoded data
|
||||
buf []byte // Serialized data
|
||||
}{
|
||||
{
|
||||
baseBlockHdr,
|
||||
baseBlockHdr,
|
||||
baseBlockHdrEncoded,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the block header.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Errorf("Serialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("Serialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the block header.
|
||||
var bh BlockHeader
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = bh.Deserialize(rbuf)
|
||||
if err != nil {
|
||||
t.Errorf("Deserialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&bh, test.out) {
|
||||
t.Errorf("Deserialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&bh), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockHeaderSerializeSize performs tests to ensure the serialize size for
|
||||
// various block headers is accurate.
|
||||
func TestBlockHeaderSerializeSize(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x1e0f3
|
||||
bits := uint32(0x1d00ffff)
|
||||
timestamp := mstime.UnixMilliseconds(0x495fab29000)
|
||||
baseBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: &daghash.ZeroHash,
|
||||
UTXOCommitment: &daghash.ZeroHash,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
genesisBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: &daghash.ZeroHash,
|
||||
UTXOCommitment: &daghash.ZeroHash,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
tests := []struct {
|
||||
in *BlockHeader // Block header to encode
|
||||
size int // Expected serialized size
|
||||
}{
|
||||
// Block with no transactions.
|
||||
{genesisBlockHdr, 121},
|
||||
|
||||
// First block in the mainnet block DAG.
|
||||
{baseBlockHdr, 185},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
serializedSize := test.in.SerializeSize()
|
||||
if serializedSize != test.size {
|
||||
t.Errorf("BlockHeader.SerializeSize: #%d got: %d, want: "+
|
||||
"%d", i, serializedSize, test.size)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsGenesis(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x1e0f3
|
||||
bits := uint32(0x1d00ffff)
|
||||
timestamp := mstime.UnixMilliseconds(0x495fab29000)
|
||||
|
||||
baseBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
genesisBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *BlockHeader // Block header to encode
|
||||
isGenesis bool // Expected result for call of .IsGenesis
|
||||
}{
|
||||
{genesisBlockHdr, true},
|
||||
{baseBlockHdr, false},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
isGenesis := test.in.IsGenesis()
|
||||
if isGenesis != test.isGenesis {
|
||||
t.Errorf("BlockHeader.IsGenesis: #%d got: %t, want: %t",
|
||||
i, isGenesis, test.isGenesis)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgIBDRootNotFound implements the Message interface and represents a kaspa
|
||||
// IBDRootNotFound message. It is used to notify the IBD root that was requested
|
||||
// by other peer was not found.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgIBDRootNotFound struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgIBDRootNotFound) Command() MessageCommand {
|
||||
return CmdIBDRootNotFound
|
||||
}
|
||||
|
||||
// NewMsgIBDRootNotFound returns a new kaspa IBDRootNotFound message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgIBDRootNotFound() *MsgDoneHeaders {
|
||||
return &MsgDoneHeaders{}
|
||||
}
|
||||
@@ -4,15 +4,58 @@
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// MaxAddressesPerMsg is the maximum number of addresses that can be in a single
|
||||
// kaspa Addresses message (MsgAddresses).
|
||||
const MaxAddressesPerMsg = 1000
|
||||
|
||||
// MsgAddresses implements the Message interface and represents a kaspa
|
||||
// Addresses message.
|
||||
// Addresses message. It is used to provide a list of known active peers on the
|
||||
// network. An active peer is considered one that has transmitted a message
|
||||
// within the last 3 hours. Nodes which have not transmitted in that time
|
||||
// frame should be forgotten. Each message is limited to a maximum number of
|
||||
// addresses, which is currently 1000. As a result, multiple messages must
|
||||
// be used to relay the full list.
|
||||
//
|
||||
// Use the AddAddress function to build up the list of known addresses when
|
||||
// sending an Addresses message to another peer.
|
||||
type MsgAddresses struct {
|
||||
baseMessage
|
||||
AddressList []*NetAddress
|
||||
IncludeAllSubnetworks bool
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
AddrList []*NetAddress
|
||||
}
|
||||
|
||||
// AddAddress adds a known active peer to the message.
|
||||
func (msg *MsgAddresses) AddAddress(na *NetAddress) error {
|
||||
if len(msg.AddrList)+1 > MaxAddressesPerMsg {
|
||||
str := fmt.Sprintf("too many addresses in message [max %d]",
|
||||
MaxAddressesPerMsg)
|
||||
return messageError("MsgAddresses.AddAddress", str)
|
||||
}
|
||||
|
||||
msg.AddrList = append(msg.AddrList, na)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAddresses adds multiple known active peers to the message.
|
||||
func (msg *MsgAddresses) AddAddresses(netAddrs ...*NetAddress) error {
|
||||
for _, na := range netAddrs {
|
||||
err := msg.AddAddress(na)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearAddresses removes all addresses from the message.
|
||||
func (msg *MsgAddresses) ClearAddresses() {
|
||||
msg.AddrList = []*NetAddress{}
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -23,8 +66,10 @@ func (msg *MsgAddresses) Command() MessageCommand {
|
||||
|
||||
// NewMsgAddresses returns a new kaspa Addresses message that conforms to the
|
||||
// Message interface. See MsgAddresses for details.
|
||||
func NewMsgAddresses(addressList []*NetAddress) *MsgAddresses {
|
||||
func NewMsgAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgAddresses {
|
||||
return &MsgAddresses{
|
||||
AddressList: addressList,
|
||||
IncludeAllSubnetworks: includeAllSubnetworks,
|
||||
SubnetworkID: subnetworkID,
|
||||
AddrList: make([]*NetAddress, 0, MaxAddressesPerMsg),
|
||||
}
|
||||
}
|
||||
|
||||
58
app/appmessage/p2p_msgaddresses_test.go
Normal file
58
app/appmessage/p2p_msgaddresses_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// TestAddresses tests the MsgAddresses API.
|
||||
func TestAddresses(t *testing.T) {
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(3)
|
||||
msg := NewMsgAddresses(false, nil)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgAddresses: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
|
||||
// Ensure NetAddresses are added properly.
|
||||
tcpAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 16111}
|
||||
na := NewNetAddress(tcpAddr, SFNodeNetwork)
|
||||
err := msg.AddAddress(na)
|
||||
if err != nil {
|
||||
t.Errorf("AddAddress: %v", err)
|
||||
}
|
||||
if msg.AddrList[0] != na {
|
||||
t.Errorf("AddAddress: wrong address added - got %v, want %v",
|
||||
spew.Sprint(msg.AddrList[0]), spew.Sprint(na))
|
||||
}
|
||||
|
||||
// Ensure the address list is cleared properly.
|
||||
msg.ClearAddresses()
|
||||
if len(msg.AddrList) != 0 {
|
||||
t.Errorf("ClearAddresses: address list is not empty - "+
|
||||
"got %v [%v], want %v", len(msg.AddrList),
|
||||
spew.Sprint(msg.AddrList[0]), 0)
|
||||
}
|
||||
|
||||
// Ensure adding more than the max allowed addresses per message returns
|
||||
// error.
|
||||
for i := 0; i < MaxAddressesPerMsg+1; i++ {
|
||||
err = msg.AddAddress(na)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("AddAddress: expected error on too many addresses " +
|
||||
"not received")
|
||||
}
|
||||
err = msg.AddAddresses(na)
|
||||
if err == nil {
|
||||
t.Errorf("AddAddresses: expected error on too many addresses " +
|
||||
"not received")
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,13 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// defaultTransactionAlloc is the default size used for the backing array
|
||||
@@ -40,7 +46,7 @@ type TxLoc struct {
|
||||
// response to a getdata message (MsgGetData) for a given block hash.
|
||||
type MsgBlock struct {
|
||||
baseMessage
|
||||
Header MsgBlockHeader
|
||||
Header BlockHeader
|
||||
Transactions []*MsgTx
|
||||
}
|
||||
|
||||
@@ -54,6 +60,161 @@ func (msg *MsgBlock) ClearTransactions() {
|
||||
msg.Transactions = make([]*MsgTx, 0, defaultTransactionAlloc)
|
||||
}
|
||||
|
||||
// KaspaDecode decodes r using the kaspa protocol encoding into the receiver.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Deserialize for decoding blocks stored to disk, such as in a database, as
|
||||
// opposed to decoding blocks from the appmessage.
|
||||
func (msg *MsgBlock) KaspaDecode(r io.Reader, pver uint32) error {
|
||||
err := readBlockHeader(r, pver, &msg.Header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txCount, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prevent more transactions than could possibly fit into a block.
|
||||
// It would be possible to cause memory exhaustion and panics without
|
||||
// a sane upper bound on this count.
|
||||
if txCount > MaxTxPerBlock {
|
||||
str := fmt.Sprintf("too many transactions to fit into a block "+
|
||||
"[count %d, max %d]", txCount, MaxTxPerBlock)
|
||||
return messageError("MsgBlock.KaspaDecode", str)
|
||||
}
|
||||
|
||||
msg.Transactions = make([]*MsgTx, 0, txCount)
|
||||
for i := uint64(0); i < txCount; i++ {
|
||||
tx := MsgTx{}
|
||||
err := tx.KaspaDecode(r, pver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg.Transactions = append(msg.Transactions, &tx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deserialize decodes a block from r into the receiver using a format that is
|
||||
// suitable for long-term storage such as a database while respecting the
|
||||
// Version field in the block. This function differs from KaspaDecode in that
|
||||
// KaspaDecode decodes from the kaspa appmessage protocol as it was sent across the
|
||||
// network. The appmessage encoding can technically differ depending on the protocol
|
||||
// version and doesn't even really need to match the format of a stored block at
|
||||
// all. As of the time this comment was written, the encoded block is the same
|
||||
// in both instances, but there is a distinct difference and separating the two
|
||||
// allows the API to be flexible enough to deal with changes.
|
||||
func (msg *MsgBlock) Deserialize(r io.Reader) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of KaspaDecode.
|
||||
return msg.KaspaDecode(r, 0)
|
||||
}
|
||||
|
||||
// DeserializeTxLoc decodes r in the same manner Deserialize does, but it takes
|
||||
// a byte buffer instead of a generic reader and returns a slice containing the
|
||||
// start and length of each transaction within the raw data that is being
|
||||
// deserialized.
|
||||
func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) {
|
||||
fullLen := r.Len()
|
||||
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of existing appmessage protocol functions.
|
||||
err := readBlockHeader(r, 0, &msg.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txCount, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prevent more transactions than could possibly fit into a block.
|
||||
// It would be possible to cause memory exhaustion and panics without
|
||||
// a sane upper bound on this count.
|
||||
if txCount > MaxTxPerBlock {
|
||||
str := fmt.Sprintf("too many transactions to fit into a block "+
|
||||
"[count %d, max %d]", txCount, MaxTxPerBlock)
|
||||
return nil, messageError("MsgBlock.DeserializeTxLoc", str)
|
||||
}
|
||||
|
||||
// Deserialize each transaction while keeping track of its location
|
||||
// within the byte stream.
|
||||
msg.Transactions = make([]*MsgTx, 0, txCount)
|
||||
txLocs := make([]TxLoc, txCount)
|
||||
for i := uint64(0); i < txCount; i++ {
|
||||
txLocs[i].TxStart = fullLen - r.Len()
|
||||
tx := MsgTx{}
|
||||
err := tx.Deserialize(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.Transactions = append(msg.Transactions, &tx)
|
||||
txLocs[i].TxLen = (fullLen - r.Len()) - txLocs[i].TxStart
|
||||
}
|
||||
|
||||
return txLocs, nil
|
||||
}
|
||||
|
||||
// KaspaEncode encodes the receiver to w using the kaspa protocol encoding.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Serialize for encoding blocks to be stored to disk, such as in a
|
||||
// database, as opposed to encoding blocks for the appmessage.
|
||||
func (msg *MsgBlock) KaspaEncode(w io.Writer, pver uint32) error {
|
||||
err := writeBlockHeader(w, pver, &msg.Header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = WriteVarInt(w, uint64(len(msg.Transactions)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, tx := range msg.Transactions {
|
||||
err = tx.KaspaEncode(w, pver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize encodes the block to w using a format that suitable for long-term
|
||||
// storage such as a database while respecting the Version field in the block.
|
||||
// This function differs from KaspaEncode in that KaspaEncode encodes the block to
|
||||
// the kaspa appmessage protocol in order to be sent across the network. The appmessage
|
||||
// encoding can technically differ depending on the protocol version and doesn't
|
||||
// even really need to match the format of a stored block at all. As of the
|
||||
// time this comment was written, the encoded block is the same in both
|
||||
// instances, but there is a distinct difference and separating the two allows
|
||||
// the API to be flexible enough to deal with changes.
|
||||
func (msg *MsgBlock) Serialize(w io.Writer) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of KaspaEncode.
|
||||
return msg.KaspaEncode(w, 0)
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize the
|
||||
// block.
|
||||
func (msg *MsgBlock) SerializeSize() int {
|
||||
// Block header bytes + Serialized varint size for the number of
|
||||
// transactions.
|
||||
n := msg.Header.SerializeSize() + VarIntSerializeSize(uint64(len(msg.Transactions)))
|
||||
|
||||
for _, tx := range msg.Transactions {
|
||||
n += tx.SerializeSize()
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgBlock) Command() MessageCommand {
|
||||
@@ -66,12 +227,17 @@ func (msg *MsgBlock) MaxPayloadLength(pver uint32) uint32 {
|
||||
return MaxMessagePayload
|
||||
}
|
||||
|
||||
// BlockHash computes the block identifier hash for this block.
|
||||
func (msg *MsgBlock) BlockHash() *daghash.Hash {
|
||||
return msg.Header.BlockHash()
|
||||
}
|
||||
|
||||
// ConvertToPartial clears out all the payloads of the subnetworks that are
|
||||
// incompatible with the given subnetwork ID.
|
||||
// Note: this operation modifies the block in place.
|
||||
func (msg *MsgBlock) ConvertToPartial(subnetworkID *externalapi.DomainSubnetworkID) {
|
||||
func (msg *MsgBlock) ConvertToPartial(subnetworkID *subnetworkid.SubnetworkID) {
|
||||
for _, tx := range msg.Transactions {
|
||||
if tx.SubnetworkID != *subnetworkID {
|
||||
if !tx.SubnetworkID.IsEqual(subnetworkID) {
|
||||
tx.Payload = []byte{}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +245,7 @@ func (msg *MsgBlock) ConvertToPartial(subnetworkID *externalapi.DomainSubnetwork
|
||||
|
||||
// NewMsgBlock returns a new kaspa block message that conforms to the
|
||||
// Message interface. See MsgBlock for details.
|
||||
func NewMsgBlock(blockHeader *MsgBlockHeader) *MsgBlock {
|
||||
func NewMsgBlock(blockHeader *BlockHeader) *MsgBlock {
|
||||
return &MsgBlock{
|
||||
Header: *blockHeader,
|
||||
Transactions: make([]*MsgTx, 0, defaultTransactionAlloc),
|
||||
|
||||
@@ -5,15 +5,17 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestBlock tests the MsgBlock API.
|
||||
@@ -69,31 +71,46 @@ func TestBlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToPartial(t *testing.T) {
|
||||
localSubnetworkID := &externalapi.DomainSubnetworkID{0x12}
|
||||
// TestBlockHash tests the ability to generate the hash of a block accurately.
|
||||
func TestBlockHash(t *testing.T) {
|
||||
// Block 1 hash.
|
||||
hashStr := "55d71bd49a8233bc9f0edbcbd0ad5d3eaebffe1fc6a6443a1c1f310fd02c11a5"
|
||||
wantHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the hash produced is expected.
|
||||
blockHash := blockOne.BlockHash()
|
||||
if !blockHash.IsEqual(wantHash) {
|
||||
t.Errorf("BlockHash: wrong hash - got %v, want %v",
|
||||
spew.Sprint(blockHash), spew.Sprint(wantHash))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToPartial(t *testing.T) {
|
||||
transactions := []struct {
|
||||
subnetworkID *externalapi.DomainSubnetworkID
|
||||
subnetworkID *subnetworkid.SubnetworkID
|
||||
payload []byte
|
||||
expectedPayloadLength int
|
||||
}{
|
||||
{
|
||||
subnetworkID: &subnetworks.SubnetworkIDNative,
|
||||
subnetworkID: subnetworkid.SubnetworkIDNative,
|
||||
payload: []byte{},
|
||||
expectedPayloadLength: 0,
|
||||
},
|
||||
{
|
||||
subnetworkID: &subnetworks.SubnetworkIDRegistry,
|
||||
subnetworkID: subnetworkid.SubnetworkIDRegistry,
|
||||
payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
|
||||
expectedPayloadLength: 0,
|
||||
},
|
||||
{
|
||||
subnetworkID: localSubnetworkID,
|
||||
subnetworkID: &subnetworkid.SubnetworkID{123},
|
||||
payload: []byte{0x01},
|
||||
expectedPayloadLength: 1,
|
||||
},
|
||||
{
|
||||
subnetworkID: &externalapi.DomainSubnetworkID{0x34},
|
||||
subnetworkID: &subnetworkid.SubnetworkID{234},
|
||||
payload: []byte{0x02},
|
||||
expectedPayloadLength: 0,
|
||||
},
|
||||
@@ -105,33 +122,388 @@ func TestConvertToPartial(t *testing.T) {
|
||||
block.Transactions = append(block.Transactions, NewSubnetworkMsgTx(1, nil, nil, transaction.subnetworkID, 0, payload))
|
||||
}
|
||||
|
||||
block.ConvertToPartial(localSubnetworkID)
|
||||
block.ConvertToPartial(&subnetworkid.SubnetworkID{123})
|
||||
|
||||
for _, testTransaction := range transactions {
|
||||
for _, transaction := range transactions {
|
||||
var subnetworkTx *MsgTx
|
||||
for _, blockTransaction := range block.Transactions {
|
||||
if blockTransaction.SubnetworkID == *testTransaction.subnetworkID {
|
||||
subnetworkTx = blockTransaction
|
||||
for _, tx := range block.Transactions {
|
||||
if tx.SubnetworkID.IsEqual(transaction.subnetworkID) {
|
||||
subnetworkTx = tx
|
||||
}
|
||||
}
|
||||
if subnetworkTx == nil {
|
||||
t.Errorf("ConvertToPartial: subnetworkID '%s' not found in block!", testTransaction.subnetworkID)
|
||||
t.Errorf("ConvertToPartial: subnetworkID '%s' not found in block!", transaction.subnetworkID)
|
||||
continue
|
||||
}
|
||||
|
||||
payloadLength := len(subnetworkTx.Payload)
|
||||
if payloadLength != testTransaction.expectedPayloadLength {
|
||||
if payloadLength != transaction.expectedPayloadLength {
|
||||
t.Errorf("ConvertToPartial: unexpected payload length for subnetwork '%s': expected: %d, got: %d",
|
||||
testTransaction.subnetworkID, testTransaction.expectedPayloadLength, payloadLength)
|
||||
transaction.subnetworkID, transaction.expectedPayloadLength, payloadLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockEncoding tests the MsgBlock appmessage encode and decode for various numbers
|
||||
// of transaction inputs and outputs and protocol versions.
|
||||
func TestBlockEncoding(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgBlock // Message to encode
|
||||
out *MsgBlock // Expected decoded message
|
||||
buf []byte // Encoded value
|
||||
txLocs []TxLoc // Expected transaction locations
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
{
|
||||
&blockOne,
|
||||
&blockOne,
|
||||
blockOneBytes,
|
||||
blockOneTxLocs,
|
||||
ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode the message to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.KaspaEncode(&buf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the message from appmessage format.
|
||||
var msg MsgBlock
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = msg.KaspaDecode(rbuf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&msg, test.out) {
|
||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockEncodingErrors performs negative tests against appmessage encode and decode
|
||||
// of MsgBlock to confirm error paths work correctly.
|
||||
func TestBlockEncodingErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
tests := []struct {
|
||||
in *MsgBlock // Value to encode
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Force error in version.
|
||||
{&blockOne, blockOneBytes, pver, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error in num block hashes.
|
||||
{&blockOne, blockOneBytes, pver, 4, io.ErrShortWrite, io.EOF},
|
||||
// Force error in prev block hash #1.
|
||||
{&blockOne, blockOneBytes, pver, 5, io.ErrShortWrite, io.EOF},
|
||||
// Force error in prev block hash #2.
|
||||
{&blockOne, blockOneBytes, pver, 37, io.ErrShortWrite, io.EOF},
|
||||
// Force error in hash merkle root.
|
||||
{&blockOne, blockOneBytes, pver, 69, io.ErrShortWrite, io.EOF},
|
||||
// Force error in accepted ID merkle root.
|
||||
{&blockOne, blockOneBytes, pver, 101, io.ErrShortWrite, io.EOF},
|
||||
// Force error in utxo commitment.
|
||||
{&blockOne, blockOneBytes, pver, 133, io.ErrShortWrite, io.EOF},
|
||||
// Force error in timestamp.
|
||||
{&blockOne, blockOneBytes, pver, 165, io.ErrShortWrite, io.EOF},
|
||||
// Force error in difficulty bits.
|
||||
{&blockOne, blockOneBytes, pver, 173, io.ErrShortWrite, io.EOF},
|
||||
// Force error in header nonce.
|
||||
{&blockOne, blockOneBytes, pver, 177, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction count.
|
||||
{&blockOne, blockOneBytes, pver, 185, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transactions.
|
||||
{&blockOne, blockOneBytes, pver, 186, io.ErrShortWrite, io.EOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
var msg MsgBlock
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockSerialize tests MsgBlock serialize and deserialize.
|
||||
func TestBlockSerialize(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgBlock // Message to encode
|
||||
out *MsgBlock // Expected decoded message
|
||||
buf []byte // Serialized data
|
||||
txLocs []TxLoc // Expected transaction locations
|
||||
}{
|
||||
{
|
||||
&blockOne,
|
||||
&blockOne,
|
||||
blockOneBytes,
|
||||
blockOneTxLocs,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the block.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Errorf("Serialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("Serialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the block.
|
||||
var block MsgBlock
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = block.Deserialize(rbuf)
|
||||
if err != nil {
|
||||
t.Errorf("Deserialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&block, test.out) {
|
||||
t.Errorf("Deserialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&block), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the block while gathering transaction location
|
||||
// information.
|
||||
var txLocBlock MsgBlock
|
||||
br := bytes.NewBuffer(test.buf)
|
||||
txLocs, err := txLocBlock.DeserializeTxLoc(br)
|
||||
if err != nil {
|
||||
t.Errorf("DeserializeTxLoc #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&txLocBlock, test.out) {
|
||||
t.Errorf("DeserializeTxLoc #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&txLocBlock), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(txLocs, test.txLocs) {
|
||||
t.Errorf("DeserializeTxLoc #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(txLocs), spew.Sdump(test.txLocs))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockSerializeErrors performs negative tests against appmessage encode and
|
||||
// decode of MsgBlock to confirm error paths work correctly.
|
||||
func TestBlockSerializeErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgBlock // Value to encode
|
||||
buf []byte // Serialized data
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Force error in version.
|
||||
{&blockOne, blockOneBytes, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error in numParentBlocks.
|
||||
{&blockOne, blockOneBytes, 4, io.ErrShortWrite, io.EOF},
|
||||
// Force error in prev block hash #1.
|
||||
{&blockOne, blockOneBytes, 5, io.ErrShortWrite, io.EOF},
|
||||
// Force error in prev block hash #2.
|
||||
{&blockOne, blockOneBytes, 37, io.ErrShortWrite, io.EOF},
|
||||
// Force error in hash merkle root.
|
||||
{&blockOne, blockOneBytes, 69, io.ErrShortWrite, io.EOF},
|
||||
// Force error in accepted ID merkle root.
|
||||
{&blockOne, blockOneBytes, 101, io.ErrShortWrite, io.EOF},
|
||||
// Force error in utxo commitment.
|
||||
{&blockOne, blockOneBytes, 133, io.ErrShortWrite, io.EOF},
|
||||
// Force error in timestamp.
|
||||
{&blockOne, blockOneBytes, 165, io.ErrShortWrite, io.EOF},
|
||||
// Force error in difficulty bits.
|
||||
{&blockOne, blockOneBytes, 173, io.ErrShortWrite, io.EOF},
|
||||
// Force error in header nonce.
|
||||
{&blockOne, blockOneBytes, 177, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction count.
|
||||
{&blockOne, blockOneBytes, 185, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transactions.
|
||||
{&blockOne, blockOneBytes, 186, io.ErrShortWrite, io.EOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the block.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.Serialize(w)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("Serialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the block.
|
||||
var block MsgBlock
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = block.Deserialize(r)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
var txLocBlock MsgBlock
|
||||
br := bytes.NewBuffer(test.buf[0:test.max])
|
||||
_, err = txLocBlock.DeserializeTxLoc(br)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("DeserializeTxLoc #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockOverflowErrors performs tests to ensure deserializing blocks which
|
||||
// are intentionally crafted to use large values for the number of transactions
|
||||
// are handled properly. This could otherwise potentially be used as an attack
|
||||
// vector.
|
||||
func TestBlockOverflowErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
tests := []struct {
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
err error // Expected error
|
||||
}{
|
||||
// Block that claims to have ~uint64(0) transactions.
|
||||
{
|
||||
[]byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||
0x02, // NumParentBlocks
|
||||
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, // mainnetGenesisHash
|
||||
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xf6, 0x7a, 0xd7, 0x69, 0x5d, 0x9b, 0x66, 0x2a, // simnetGenesisHash
|
||||
0x72, 0xff, 0x3d, 0x8e, 0xdb, 0xbb, 0x2d, 0xe0,
|
||||
0xbf, 0xa6, 0x7b, 0x13, 0x97, 0x4b, 0xb9, 0x91,
|
||||
0x0d, 0x11, 0x6d, 0x5c, 0xbd, 0x86, 0x3e, 0x68,
|
||||
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, // HashMerkleRoot
|
||||
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a,
|
||||
0x09, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // AcceptedIDMerkleRoot
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x10, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // UTXOCommitment
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x61, 0xbc, 0x66, 0x49, 0x00, 0x00, 0x00, 0x00, // Timestamp
|
||||
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||
0x01, 0xe3, 0x62, 0x99, 0x00, 0x00, 0x00, 0x00, // Fake Nonce
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // TxnCount
|
||||
}, pver, &MessageError{},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Decode from appmessage format.
|
||||
var msg MsgBlock
|
||||
r := bytes.NewReader(test.buf)
|
||||
err := msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize from appmessage format.
|
||||
r = bytes.NewReader(test.buf)
|
||||
err = msg.Deserialize(r)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize with transaction location info from appmessage format.
|
||||
br := bytes.NewBuffer(test.buf)
|
||||
_, err = msg.DeserializeTxLoc(br)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("DeserializeTxLoc #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockSerializeSize performs tests to ensure the serialize size for
|
||||
// various blocks is accurate.
|
||||
func TestBlockSerializeSize(t *testing.T) {
|
||||
// Block with no transactions.
|
||||
noTxBlock := NewMsgBlock(&blockOne.Header)
|
||||
|
||||
tests := []struct {
|
||||
in *MsgBlock // Block to encode
|
||||
size int // Expected serialized size
|
||||
}{
|
||||
// Block with no transactions.
|
||||
{noTxBlock, 186},
|
||||
|
||||
// First block in the mainnet block DAG.
|
||||
{&blockOne, len(blockOneBytes)},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
serializedSize := test.in.SerializeSize()
|
||||
if serializedSize != test.size {
|
||||
t.Errorf("MsgBlock.SerializeSize: #%d got: %d, want: "+
|
||||
"%d", i, serializedSize, test.size)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// blockOne is the first block in the mainnet block DAG.
|
||||
var blockOne = MsgBlock{
|
||||
Header: MsgBlockHeader{
|
||||
Header: BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*externalapi.DomainHash{mainnetGenesisHash, simnetGenesisHash},
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: exampleAcceptedIDMerkleRoot,
|
||||
UTXOCommitment: exampleUTXOCommitment,
|
||||
@@ -144,7 +516,7 @@ var blockOne = MsgBlock{
|
||||
[]*TxIn{
|
||||
{
|
||||
PreviousOutpoint: Outpoint{
|
||||
TxID: externalapi.DomainTransactionID{},
|
||||
TxID: daghash.TxID{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
SignatureScript: []byte{
|
||||
|
||||
@@ -1,108 +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 (
|
||||
"math"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// BaseBlockHeaderPayload is the base number of bytes a block header can be,
|
||||
// not including the list of parent block headers.
|
||||
// Version 4 bytes + Timestamp 8 bytes + Bits 4 bytes + Nonce 8 bytes +
|
||||
// + NumParentBlocks 1 byte + HashMerkleRoot hash +
|
||||
// + AcceptedIDMerkleRoot hash + UTXOCommitment hash.
|
||||
// To get total size of block header len(ParentHashes) * externalapi.DomainHashSize should be
|
||||
// added to this value
|
||||
const BaseBlockHeaderPayload = 25 + 3*(externalapi.DomainHashSize)
|
||||
|
||||
// MaxNumParentBlocks is the maximum number of parent blocks a block can reference.
|
||||
// Currently set to 255 as the maximum number NumParentBlocks can be due to it being a byte
|
||||
const MaxNumParentBlocks = 255
|
||||
|
||||
// MaxBlockHeaderPayload is the maximum number of bytes a block header can be.
|
||||
// BaseBlockHeaderPayload + up to MaxNumParentBlocks hashes of parent blocks
|
||||
const MaxBlockHeaderPayload = BaseBlockHeaderPayload + (MaxNumParentBlocks * externalapi.DomainHashSize)
|
||||
|
||||
// MsgBlockHeader defines information about a block and is used in the kaspa
|
||||
// block (MsgBlock) and headers (MsgHeader) messages.
|
||||
type MsgBlockHeader struct {
|
||||
baseMessage
|
||||
|
||||
// Version of the block. This is not the same as the protocol version.
|
||||
Version int32
|
||||
|
||||
// Hashes of the parent block headers in the blockDAG.
|
||||
ParentHashes []*externalapi.DomainHash
|
||||
|
||||
// HashMerkleRoot is the merkle tree reference to hash of all transactions for the block.
|
||||
HashMerkleRoot *externalapi.DomainHash
|
||||
|
||||
// AcceptedIDMerkleRoot is merkle tree reference to hash all transactions
|
||||
// accepted form the block.Blues
|
||||
AcceptedIDMerkleRoot *externalapi.DomainHash
|
||||
|
||||
// UTXOCommitment is an ECMH UTXO commitment to the block UTXO.
|
||||
UTXOCommitment *externalapi.DomainHash
|
||||
|
||||
// Time the block was created.
|
||||
Timestamp mstime.Time
|
||||
|
||||
// Difficulty target for the block.
|
||||
Bits uint32
|
||||
|
||||
// Nonce used to generate the block.
|
||||
Nonce uint64
|
||||
}
|
||||
|
||||
// NumParentBlocks return the number of entries in ParentHashes
|
||||
func (h *MsgBlockHeader) NumParentBlocks() byte {
|
||||
numParents := len(h.ParentHashes)
|
||||
if numParents > math.MaxUint8 {
|
||||
panic(errors.Errorf("number of parents is %d, which is more than one byte can fit", numParents))
|
||||
}
|
||||
return byte(numParents)
|
||||
}
|
||||
|
||||
// BlockHash computes the block identifier hash for the given block header.
|
||||
func (h *MsgBlockHeader) BlockHash() *externalapi.DomainHash {
|
||||
return consensusserialization.HeaderHash(BlockHeaderToDomainBlockHeader(h))
|
||||
}
|
||||
|
||||
// IsGenesis returns true iff this block is a genesis block
|
||||
func (h *MsgBlockHeader) IsGenesis() bool {
|
||||
return h.NumParentBlocks() == 0
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (h *MsgBlockHeader) Command() MessageCommand {
|
||||
return CmdHeader
|
||||
}
|
||||
|
||||
// NewBlockHeader returns a new MsgBlockHeader using the provided version, previous
|
||||
// block hash, hash merkle root, accepted ID merkle root, difficulty bits, and nonce used to generate the
|
||||
// block with defaults or calclulated values for the remaining fields.
|
||||
func NewBlockHeader(version int32, parentHashes []*externalapi.DomainHash, hashMerkleRoot *externalapi.DomainHash,
|
||||
acceptedIDMerkleRoot *externalapi.DomainHash, utxoCommitment *externalapi.DomainHash, bits uint32, nonce uint64) *MsgBlockHeader {
|
||||
|
||||
// Limit the timestamp to one millisecond precision since the protocol
|
||||
// doesn't support better.
|
||||
return &MsgBlockHeader{
|
||||
Version: version,
|
||||
ParentHashes: parentHashes,
|
||||
HashMerkleRoot: hashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: acceptedIDMerkleRoot,
|
||||
UTXOCommitment: utxoCommitment,
|
||||
Timestamp: mstime.Now(),
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
}
|
||||
@@ -1,88 +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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/random"
|
||||
)
|
||||
|
||||
// TestBlockHeader tests the MsgBlockHeader API.
|
||||
func TestBlockHeader(t *testing.T) {
|
||||
nonce, err := random.Uint64()
|
||||
if err != nil {
|
||||
t.Errorf("random.Uint64: Error generating nonce: %v", err)
|
||||
}
|
||||
|
||||
hashes := []*externalapi.DomainHash{mainnetGenesisHash, simnetGenesisHash}
|
||||
|
||||
merkleHash := mainnetGenesisMerkleRoot
|
||||
acceptedIDMerkleRoot := exampleAcceptedIDMerkleRoot
|
||||
bits := uint32(0x1d00ffff)
|
||||
bh := NewBlockHeader(1, hashes, merkleHash, acceptedIDMerkleRoot, exampleUTXOCommitment, bits, nonce)
|
||||
|
||||
// Ensure we get the same data back out.
|
||||
if !reflect.DeepEqual(bh.ParentHashes, hashes) {
|
||||
t.Errorf("NewBlockHeader: wrong prev hashes - got %v, want %v",
|
||||
spew.Sprint(bh.ParentHashes), spew.Sprint(hashes))
|
||||
}
|
||||
if bh.HashMerkleRoot != merkleHash {
|
||||
t.Errorf("NewBlockHeader: wrong merkle root - got %v, want %v",
|
||||
spew.Sprint(bh.HashMerkleRoot), spew.Sprint(merkleHash))
|
||||
}
|
||||
if bh.Bits != bits {
|
||||
t.Errorf("NewBlockHeader: wrong bits - got %v, want %v",
|
||||
bh.Bits, bits)
|
||||
}
|
||||
if bh.Nonce != nonce {
|
||||
t.Errorf("NewBlockHeader: wrong nonce - got %v, want %v",
|
||||
bh.Nonce, nonce)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsGenesis(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x1e0f3
|
||||
bits := uint32(0x1d00ffff)
|
||||
timestamp := mstime.UnixMilliseconds(0x495fab29000)
|
||||
|
||||
baseBlockHdr := &MsgBlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*externalapi.DomainHash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
genesisBlockHdr := &MsgBlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*externalapi.DomainHash{},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *MsgBlockHeader // Block header to encode
|
||||
isGenesis bool // Expected result for call of .IsGenesis
|
||||
}{
|
||||
{genesisBlockHdr, true},
|
||||
{baseBlockHdr, false},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
isGenesis := test.in.IsGenesis()
|
||||
if isGenesis != test.isGenesis {
|
||||
t.Errorf("MsgBlockHeader.IsGenesis: #%d got: %t, want: %t",
|
||||
i, isGenesis, test.isGenesis)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MaxBlockLocatorsPerMsg is the maximum number of block locator hashes allowed
|
||||
@@ -13,7 +13,7 @@ const MaxBlockLocatorsPerMsg = 500
|
||||
// syncing with you.
|
||||
type MsgBlockLocator struct {
|
||||
baseMessage
|
||||
BlockLocatorHashes []*externalapi.DomainHash
|
||||
BlockLocatorHashes []*daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -24,7 +24,7 @@ func (msg *MsgBlockLocator) Command() MessageCommand {
|
||||
|
||||
// NewMsgBlockLocator returns a new kaspa locator message that conforms to
|
||||
// the Message interface. See MsgBlockLocator for details.
|
||||
func NewMsgBlockLocator(locatorHashes []*externalapi.DomainHash) *MsgBlockLocator {
|
||||
func NewMsgBlockLocator(locatorHashes []*daghash.Hash) *MsgBlockLocator {
|
||||
return &MsgBlockLocator{
|
||||
BlockLocatorHashes: locatorHashes,
|
||||
}
|
||||
|
||||
@@ -3,22 +3,19 @@ package appmessage
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestBlockLocator tests the MsgBlockLocator API.
|
||||
func TestBlockLocator(t *testing.T) {
|
||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||
locatorHash, err := hashes.FromString(hashStr)
|
||||
locatorHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
msg := NewMsgBlockLocator([]*externalapi.DomainHash{locatorHash})
|
||||
msg := NewMsgBlockLocator([]*daghash.Hash{locatorHash})
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(10)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgDoneHeaders implements the Message interface and represents a kaspa
|
||||
// DoneHeaders message. It is used to notify the IBD syncing peer that the
|
||||
// syncer sent all the requested headers.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgDoneHeaders struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgDoneHeaders) Command() MessageCommand {
|
||||
return CmdDoneHeaders
|
||||
}
|
||||
|
||||
// NewMsgDoneHeaders returns a new kaspa DoneIBDBlocks message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgDoneHeaders() *MsgDoneHeaders {
|
||||
return &MsgDoneHeaders{}
|
||||
}
|
||||
22
app/appmessage/p2p_msgdoneibdblocks.go
Normal file
22
app/appmessage/p2p_msgdoneibdblocks.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package appmessage
|
||||
|
||||
// MsgDoneIBDBlocks implements the Message interface and represents a kaspa
|
||||
// DoneIBDBlocks message. It is used to notify the IBD syncing peer that the
|
||||
// syncer sent all the requested blocks.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgDoneIBDBlocks struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgDoneIBDBlocks) Command() MessageCommand {
|
||||
return CmdDoneIBDBlocks
|
||||
}
|
||||
|
||||
// NewMsgDoneIBDBlocks returns a new kaspa DoneIBDBlocks message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgDoneIBDBlocks() *MsgDoneIBDBlocks {
|
||||
return &MsgDoneIBDBlocks{}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -63,3 +64,55 @@ func TestIBDBlock(t *testing.T) {
|
||||
len(msg.Transactions), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIBDBlockEncoding tests the MsgIBDBlock appmessage encode and decode for various numbers
|
||||
// of transaction inputs and outputs and protocol versions.
|
||||
func TestIBDBlockEncoding(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgIBDBlock // Message to encode
|
||||
out *MsgIBDBlock // Expected decoded message
|
||||
buf []byte // Encoded value
|
||||
txLocs []TxLoc // Expected transaction locations
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
{
|
||||
&MsgIBDBlock{MsgBlock: &blockOne},
|
||||
&MsgIBDBlock{MsgBlock: &blockOne},
|
||||
blockOneBytes,
|
||||
blockOneTxLocs,
|
||||
ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode the message to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.KaspaEncode(&buf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the message from appmessage format.
|
||||
var msg MsgIBDBlock
|
||||
msg.MsgBlock = new(MsgBlock)
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = msg.KaspaDecode(rbuf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&msg, test.out) {
|
||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgIBDRootUTXOSetAndBlock implements the Message interface and represents a kaspa
|
||||
// IBDRootUTXOSetAndBlock message. It is used to answer RequestIBDRootUTXOSetAndBlock messages.
|
||||
type MsgIBDRootUTXOSetAndBlock struct {
|
||||
baseMessage
|
||||
UTXOSet []byte
|
||||
Block *MsgBlock
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgIBDRootUTXOSetAndBlock) Command() MessageCommand {
|
||||
return CmdIBDRootUTXOSetAndBlock
|
||||
}
|
||||
|
||||
// NewMsgIBDRootUTXOSetAndBlock returns a new MsgIBDRootUTXOSetAndBlock.
|
||||
func NewMsgIBDRootUTXOSetAndBlock(utxoSet []byte, block *MsgBlock) *MsgIBDRootUTXOSetAndBlock {
|
||||
return &MsgIBDRootUTXOSetAndBlock{
|
||||
UTXOSet: utxoSet,
|
||||
Block: block,
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgInvRelayBlock implements the Message interface and represents a kaspa
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
// by sending their hash, and let the receiving node decide if it needs it.
|
||||
type MsgInvRelayBlock struct {
|
||||
baseMessage
|
||||
Hash *externalapi.DomainHash
|
||||
Hash *daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -20,7 +20,7 @@ func (msg *MsgInvRelayBlock) Command() MessageCommand {
|
||||
|
||||
// NewMsgInvBlock returns a new kaspa invrelblk message that conforms to
|
||||
// the Message interface. See MsgInvRelayBlock for details.
|
||||
func NewMsgInvBlock(hash *externalapi.DomainHash) *MsgInvRelayBlock {
|
||||
func NewMsgInvBlock(hash *daghash.Hash) *MsgInvRelayBlock {
|
||||
return &MsgInvRelayBlock{
|
||||
Hash: hash,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MaxInvPerTxInvMsg is the maximum number of hashes that can
|
||||
@@ -13,7 +13,7 @@ const MaxInvPerTxInvMsg = MaxInvPerMsg
|
||||
// by sending their ID, and let the receiving node decide if it needs it.
|
||||
type MsgInvTransaction struct {
|
||||
baseMessage
|
||||
TxIDs []*externalapi.DomainTransactionID
|
||||
TxIDs []*daghash.TxID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -24,7 +24,7 @@ func (msg *MsgInvTransaction) Command() MessageCommand {
|
||||
|
||||
// NewMsgInvTransaction returns a new kaspa TxInv message that conforms to
|
||||
// the Message interface. See MsgInvTransaction for details.
|
||||
func NewMsgInvTransaction(ids []*externalapi.DomainTransactionID) *MsgInvTransaction {
|
||||
func NewMsgInvTransaction(ids []*daghash.TxID) *MsgInvTransaction {
|
||||
return &MsgInvTransaction{
|
||||
TxIDs: ids,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// MsgRequestAddresses implements the Message interface and represents a kaspa
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
type MsgRequestAddresses struct {
|
||||
baseMessage
|
||||
IncludeAllSubnetworks bool
|
||||
SubnetworkID *externalapi.DomainSubnetworkID
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -28,7 +28,7 @@ func (msg *MsgRequestAddresses) Command() MessageCommand {
|
||||
|
||||
// NewMsgRequestAddresses returns a new kaspa RequestAddresses message that conforms to the
|
||||
// Message interface. See MsgRequestAddresses for details.
|
||||
func NewMsgRequestAddresses(includeAllSubnetworks bool, subnetworkID *externalapi.DomainSubnetworkID) *MsgRequestAddresses {
|
||||
func NewMsgRequestAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgRequestAddresses {
|
||||
return &MsgRequestAddresses{
|
||||
IncludeAllSubnetworks: includeAllSubnetworks,
|
||||
SubnetworkID: subnetworkID,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgRequestBlockLocator implements the Message interface and represents a kaspa
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
// The locator is returned via a locator message (MsgBlockLocator).
|
||||
type MsgRequestBlockLocator struct {
|
||||
baseMessage
|
||||
HighHash *externalapi.DomainHash
|
||||
LowHash *externalapi.DomainHash
|
||||
HighHash *daghash.Hash
|
||||
LowHash *daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -23,7 +23,7 @@ func (msg *MsgRequestBlockLocator) Command() MessageCommand {
|
||||
// NewMsgRequestBlockLocator returns a new RequestBlockLocator message that conforms to the
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgRequestBlockLocator(highHash, lowHash *externalapi.DomainHash) *MsgRequestBlockLocator {
|
||||
func NewMsgRequestBlockLocator(highHash, lowHash *daghash.Hash) *MsgRequestBlockLocator {
|
||||
return &MsgRequestBlockLocator{
|
||||
HighHash: highHash,
|
||||
LowHash: lowHash,
|
||||
|
||||
@@ -3,22 +3,20 @@ package appmessage
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestRequestBlockLocator tests the MsgRequestBlockLocator API.
|
||||
func TestRequestBlockLocator(t *testing.T) {
|
||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||
highHash, err := hashes.FromString(hashStr)
|
||||
highHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(9)
|
||||
msg := NewMsgRequestBlockLocator(highHash, &externalapi.DomainHash{})
|
||||
msg := NewMsgRequestBlockLocator(highHash, &daghash.ZeroHash)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgRequestBlockLocator: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestHeaders implements the Message interface and represents a kaspa
|
||||
// RequestHeaders message. It is used to request a list of blocks starting after the
|
||||
// low hash and until the high hash.
|
||||
type MsgRequestHeaders struct {
|
||||
baseMessage
|
||||
LowHash *externalapi.DomainHash
|
||||
HighHash *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestHeaders) Command() MessageCommand {
|
||||
return CmdRequestHeaders
|
||||
}
|
||||
|
||||
// NewMsgRequstHeaders returns a new kaspa RequestHeaders message that conforms to the
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgRequstHeaders(lowHash, highHash *externalapi.DomainHash) *MsgRequestHeaders {
|
||||
return &MsgRequestHeaders{
|
||||
LowHash: lowHash,
|
||||
HighHash: highHash,
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MaxRequestIBDBlocksHashes is the maximum number of hashes that can
|
||||
// be in a single RequestIBDBlocks message.
|
||||
const MaxRequestIBDBlocksHashes = MaxInvPerMsg
|
||||
|
||||
// MsgRequestIBDBlocks implements the Message interface and represents a kaspa
|
||||
// RequestIBDBlocks message. It is used to request blocks as part of the IBD
|
||||
// protocol.
|
||||
// RequestIBDBlocks message. It is used to request a list of blocks starting after the
|
||||
// low hash and until the high hash.
|
||||
type MsgRequestIBDBlocks struct {
|
||||
baseMessage
|
||||
Hashes []*externalapi.DomainHash
|
||||
LowHash *daghash.Hash
|
||||
HighHash *daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -22,9 +23,12 @@ func (msg *MsgRequestIBDBlocks) Command() MessageCommand {
|
||||
return CmdRequestIBDBlocks
|
||||
}
|
||||
|
||||
// NewMsgRequestIBDBlocks returns a new MsgRequestIBDBlocks.
|
||||
func NewMsgRequestIBDBlocks(hashes []*externalapi.DomainHash) *MsgRequestIBDBlocks {
|
||||
// NewMsgRequstIBDBlocks returns a new kaspa RequestIBDBlocks message that conforms to the
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgRequstIBDBlocks(lowHash, highHash *daghash.Hash) *MsgRequestIBDBlocks {
|
||||
return &MsgRequestIBDBlocks{
|
||||
Hashes: hashes,
|
||||
LowHash: lowHash,
|
||||
HighHash: highHash,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,34 +7,34 @@ package appmessage
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestRequstIBDBlocks tests the MsgRequestHeaders API.
|
||||
// TestRequstIBDBlocks tests the MsgRequestIBDBlocks API.
|
||||
func TestRequstIBDBlocks(t *testing.T) {
|
||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||
lowHash, err := hashes.FromString(hashStr)
|
||||
lowHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
hashStr = "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
highHash, err := hashes.FromString(hashStr)
|
||||
highHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
// Ensure we get the same data back out.
|
||||
msg := NewMsgRequstHeaders(lowHash, highHash)
|
||||
if *msg.HighHash != *highHash {
|
||||
t.Errorf("NewMsgRequstHeaders: wrong high hash - got %v, want %v",
|
||||
msg := NewMsgRequstIBDBlocks(lowHash, highHash)
|
||||
if !msg.HighHash.IsEqual(highHash) {
|
||||
t.Errorf("NewMsgRequstIBDBlocks: wrong high hash - got %v, want %v",
|
||||
msg.HighHash, highHash)
|
||||
}
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(4)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgRequstHeaders: wrong command - got %v want %v",
|
||||
t.Errorf("NewMsgRequstIBDBlocks: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestIBDRootUTXOSetAndBlock implements the Message interface and represents a kaspa
|
||||
// RequestIBDRootUTXOSetAndBlock message. It is used to request the UTXO set and block body
|
||||
// of the IBD root block.
|
||||
type MsgRequestIBDRootUTXOSetAndBlock struct {
|
||||
baseMessage
|
||||
IBDRoot *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestIBDRootUTXOSetAndBlock) Command() MessageCommand {
|
||||
return CmdRequestIBDRootUTXOSetAndBlock
|
||||
}
|
||||
|
||||
// NewMsgRequestIBDRootUTXOSetAndBlock returns a new MsgRequestIBDRootUTXOSetAndBlock.
|
||||
func NewMsgRequestIBDRootUTXOSetAndBlock(ibdRoot *externalapi.DomainHash) *MsgRequestIBDRootUTXOSetAndBlock {
|
||||
return &MsgRequestIBDRootUTXOSetAndBlock{
|
||||
IBDRoot: ibdRoot,
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestNextHeaders implements the Message interface and represents a kaspa
|
||||
// RequestNextHeaders message. It is used to notify the IBD syncer peer to send
|
||||
// more headers.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgRequestNextHeaders struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestNextHeaders) Command() MessageCommand {
|
||||
return CmdRequestNextHeaders
|
||||
}
|
||||
|
||||
// NewMsgRequestNextHeaders returns a new kaspa RequestNextHeaders message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgRequestNextHeaders() *MsgRequestNextHeaders {
|
||||
return &MsgRequestNextHeaders{}
|
||||
}
|
||||
22
app/appmessage/p2p_msgrequestnextibdblocks.go
Normal file
22
app/appmessage/p2p_msgrequestnextibdblocks.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestNextIBDBlocks implements the Message interface and represents a kaspa
|
||||
// RequestNextIBDBlocks message. It is used to notify the IBD syncer peer to send
|
||||
// more blocks.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgRequestNextIBDBlocks struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestNextIBDBlocks) Command() MessageCommand {
|
||||
return CmdRequestNextIBDBlocks
|
||||
}
|
||||
|
||||
// NewMsgRequestNextIBDBlocks returns a new kaspa RequestNextIBDBlocks message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgRequestNextIBDBlocks() *MsgRequestNextIBDBlocks {
|
||||
return &MsgRequestNextIBDBlocks{}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MaxRequestRelayBlocksHashes is the maximum number of hashes that can
|
||||
// MsgRequestRelayBlocksHashes is the maximum number of hashes that can
|
||||
// be in a single RequestRelayBlocks message.
|
||||
const MaxRequestRelayBlocksHashes = MaxInvPerMsg
|
||||
const MsgRequestRelayBlocksHashes = MaxInvPerMsg
|
||||
|
||||
// MsgRequestRelayBlocks implements the Message interface and represents a kaspa
|
||||
// RequestRelayBlocks message. It is used to request blocks as part of the block
|
||||
// relay protocol.
|
||||
type MsgRequestRelayBlocks struct {
|
||||
baseMessage
|
||||
Hashes []*externalapi.DomainHash
|
||||
Hashes []*daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -24,7 +24,7 @@ func (msg *MsgRequestRelayBlocks) Command() MessageCommand {
|
||||
|
||||
// NewMsgRequestRelayBlocks returns a new kaspa RequestRelayBlocks message that conforms to
|
||||
// the Message interface. See MsgRequestRelayBlocks for details.
|
||||
func NewMsgRequestRelayBlocks(hashes []*externalapi.DomainHash) *MsgRequestRelayBlocks {
|
||||
func NewMsgRequestRelayBlocks(hashes []*daghash.Hash) *MsgRequestRelayBlocks {
|
||||
return &MsgRequestRelayBlocks{
|
||||
Hashes: hashes,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MaxInvPerRequestTransactionsMsg is the maximum number of hashes that can
|
||||
@@ -13,7 +13,7 @@ const MaxInvPerRequestTransactionsMsg = MaxInvPerMsg
|
||||
// transactions relay protocol.
|
||||
type MsgRequestTransactions struct {
|
||||
baseMessage
|
||||
IDs []*externalapi.DomainTransactionID
|
||||
IDs []*daghash.TxID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -24,7 +24,7 @@ func (msg *MsgRequestTransactions) Command() MessageCommand {
|
||||
|
||||
// NewMsgRequestTransactions returns a new kaspa RequestTransactions message that conforms to
|
||||
// the Message interface. See MsgRequestTransactions for details.
|
||||
func NewMsgRequestTransactions(ids []*externalapi.DomainTransactionID) *MsgRequestTransactions {
|
||||
func NewMsgRequestTransactions(ids []*daghash.TxID) *MsgRequestTransactions {
|
||||
return &MsgRequestTransactions{
|
||||
IDs: ids,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgSelectedTip implements the Message interface and represents a kaspa
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
type MsgSelectedTip struct {
|
||||
baseMessage
|
||||
// The selected tip hash of the generator of the message.
|
||||
SelectedTipHash *externalapi.DomainHash
|
||||
SelectedTipHash *daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -21,7 +21,7 @@ func (msg *MsgSelectedTip) Command() MessageCommand {
|
||||
|
||||
// NewMsgSelectedTip returns a new kaspa selectedtip message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgSelectedTip(selectedTipHash *externalapi.DomainHash) *MsgSelectedTip {
|
||||
func NewMsgSelectedTip(selectedTipHash *daghash.Hash) *MsgSelectedTip {
|
||||
return &MsgSelectedTip{
|
||||
SelectedTipHash: selectedTipHash,
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// TestSelectedTip tests the MsgSelectedTip API.
|
||||
func TestSelectedTip(t *testing.T) {
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(11)
|
||||
msg := NewMsgSelectedTip(&externalapi.DomainHash{})
|
||||
msg := NewMsgSelectedTip(&daghash.ZeroHash)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgSelectedTip: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgTransactionNotFound defines a kaspa TransactionNotFound message which is sent in response to
|
||||
// a RequestTransactions message if any of the requested data in not available on the peer.
|
||||
type MsgTransactionNotFound struct {
|
||||
baseMessage
|
||||
ID *externalapi.DomainTransactionID
|
||||
ID *daghash.TxID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -23,7 +23,7 @@ func (msg *MsgTransactionNotFound) Command() MessageCommand {
|
||||
|
||||
// NewMsgTransactionNotFound returns a new kaspa transactionsnotfound message that conforms to the
|
||||
// Message interface. See MsgTransactionNotFound for details.
|
||||
func NewMsgTransactionNotFound(id *externalapi.DomainTransactionID) *MsgTransactionNotFound {
|
||||
func NewMsgTransactionNotFound(id *daghash.TxID) *MsgTransactionNotFound {
|
||||
return &MsgTransactionNotFound{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
@@ -6,24 +6,49 @@ package appmessage
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/binaryserializer"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
const (
|
||||
// TxVersion is the current latest supported transaction version.
|
||||
TxVersion = 1
|
||||
|
||||
// MaxTxInSequenceNum is the maximum sequence number the sequence field
|
||||
// of a transaction input can be.
|
||||
MaxTxInSequenceNum uint64 = math.MaxUint64
|
||||
|
||||
// MaxPrevOutIndex is the maximum index the index field of a previous
|
||||
// outpoint can be.
|
||||
MaxPrevOutIndex uint32 = 0xffffffff
|
||||
|
||||
// SequenceLockTimeDisabled is a flag that if set on a transaction
|
||||
// input's sequence number, the sequence number will not be interpreted
|
||||
// as a relative locktime.
|
||||
SequenceLockTimeDisabled = 1 << 31
|
||||
|
||||
// SequenceLockTimeIsSeconds is a flag that if set on a transaction
|
||||
// input's sequence number, the relative locktime has units of 512
|
||||
// seconds.
|
||||
SequenceLockTimeIsSeconds = 1 << 22
|
||||
|
||||
// SequenceLockTimeMask is a mask that extracts the relative locktime
|
||||
// when masked against the transaction input sequence number.
|
||||
SequenceLockTimeMask = 0x0000ffff
|
||||
|
||||
// SequenceLockTimeGranularity is the defined time based granularity
|
||||
// for milliseconds-based relative time locks. When converting from milliseconds
|
||||
// to a sequence number, the value is right shifted by this amount,
|
||||
// therefore the granularity of relative time locks in 524288 or 2^19
|
||||
// seconds. Enforced relative lock times are multiples of 524288 milliseconds.
|
||||
SequenceLockTimeGranularity = 19
|
||||
|
||||
// defaultTxInOutAlloc is the default size used for the backing array for
|
||||
// transaction inputs and outputs. The array will dynamically grow as needed,
|
||||
// but this figure is intended to provide enough space for the number of
|
||||
@@ -34,7 +59,7 @@ const (
|
||||
// minTxInPayload is the minimum payload size for a transaction input.
|
||||
// PreviousOutpoint.TxID + PreviousOutpoint.Index 4 bytes + Varint for
|
||||
// SignatureScript length 1 byte + Sequence 4 bytes.
|
||||
minTxInPayload = 9 + externalapi.DomainHashSize
|
||||
minTxInPayload = 9 + daghash.HashSize
|
||||
|
||||
// maxTxInPerMessage is the maximum number of transactions inputs that
|
||||
// a transaction which fits into a message could possibly have.
|
||||
@@ -56,18 +81,102 @@ const (
|
||||
// number of transaction outputs 1 byte + LockTime 4 bytes + min input
|
||||
// payload + min output payload.
|
||||
minTxPayload = 10
|
||||
|
||||
// freeListMaxScriptSize is the size of each buffer in the free list
|
||||
// that is used for deserializing scripts from the appmessage before they are
|
||||
// concatenated into a single contiguous buffers. This value was chosen
|
||||
// because it is slightly more than twice the size of the vast majority
|
||||
// of all "standard" scripts. Larger scripts are still deserialized
|
||||
// properly as the free list will simply be bypassed for them.
|
||||
freeListMaxScriptSize = 512
|
||||
|
||||
// freeListMaxItems is the number of buffers to keep in the free list
|
||||
// to use for script deserialization. This value allows up to 100
|
||||
// scripts per transaction being simultaneously deserialized by 125
|
||||
// peers. Thus, the peak usage of the free list is 12,500 * 512 =
|
||||
// 6,400,000 bytes.
|
||||
freeListMaxItems = 12500
|
||||
)
|
||||
|
||||
// txEncoding is a bitmask defining which transaction fields we
|
||||
// want to encode and which to ignore.
|
||||
type txEncoding uint8
|
||||
|
||||
const (
|
||||
txEncodingFull txEncoding = 0
|
||||
|
||||
txEncodingExcludePayload txEncoding = 1 << iota
|
||||
|
||||
txEncodingExcludeSignatureScript
|
||||
)
|
||||
|
||||
// scriptFreeList defines a free list of byte slices (up to the maximum number
|
||||
// defined by the freeListMaxItems constant) that have a cap according to the
|
||||
// freeListMaxScriptSize constant. It is used to provide temporary buffers for
|
||||
// deserializing scripts in order to greatly reduce the number of allocations
|
||||
// required.
|
||||
//
|
||||
// The caller can obtain a buffer from the free list by calling the Borrow
|
||||
// function and should return it via the Return function when done using it.
|
||||
type scriptFreeList chan []byte
|
||||
|
||||
// Borrow returns a byte slice from the free list with a length according the
|
||||
// provided size. A new buffer is allocated if there are any items available.
|
||||
//
|
||||
// When the size is larger than the max size allowed for items on the free list
|
||||
// a new buffer of the appropriate size is allocated and returned. It is safe
|
||||
// to attempt to return said buffer via the Return function as it will be
|
||||
// ignored and allowed to go the garbage collector.
|
||||
func (c scriptFreeList) Borrow(size uint64) []byte {
|
||||
if size > freeListMaxScriptSize {
|
||||
return make([]byte, size)
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
select {
|
||||
case buf = <-c:
|
||||
default:
|
||||
buf = make([]byte, freeListMaxScriptSize)
|
||||
}
|
||||
return buf[:size]
|
||||
}
|
||||
|
||||
// Return puts the provided byte slice back on the free list when it has a cap
|
||||
// of the expected length. The buffer is expected to have been obtained via
|
||||
// the Borrow function. Any slices that are not of the appropriate size, such
|
||||
// as those whose size is greater than the largest allowed free list item size
|
||||
// are simply ignored so they can go to the garbage collector.
|
||||
func (c scriptFreeList) Return(buf []byte) {
|
||||
// Ignore any buffers returned that aren't the expected size for the
|
||||
// free list.
|
||||
if cap(buf) != freeListMaxScriptSize {
|
||||
return
|
||||
}
|
||||
|
||||
// Return the buffer to the free list when it's not full. Otherwise let
|
||||
// it be garbage collected.
|
||||
select {
|
||||
case c <- buf:
|
||||
default:
|
||||
// Let it go to the garbage collector.
|
||||
}
|
||||
}
|
||||
|
||||
// Create the concurrent safe free list to use for script deserialization. As
|
||||
// previously described, this free list is maintained to significantly reduce
|
||||
// the number of allocations.
|
||||
var scriptPool scriptFreeList = make(chan []byte, freeListMaxItems)
|
||||
|
||||
// Outpoint defines a kaspa data type that is used to track previous
|
||||
// transaction outputs.
|
||||
type Outpoint struct {
|
||||
TxID externalapi.DomainTransactionID
|
||||
TxID daghash.TxID
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// NewOutpoint returns a new kaspa transaction outpoint point with the
|
||||
// provided hash and index.
|
||||
func NewOutpoint(txID *externalapi.DomainTransactionID, index uint32) *Outpoint {
|
||||
func NewOutpoint(txID *daghash.TxID, index uint32) *Outpoint {
|
||||
return &Outpoint{
|
||||
TxID: *txID,
|
||||
Index: index,
|
||||
@@ -82,9 +191,9 @@ func (o Outpoint) String() string {
|
||||
// maximum message payload may increase in the future and this
|
||||
// optimization may go unnoticed, so allocate space for 10 decimal
|
||||
// digits, which will fit any uint32.
|
||||
buf := make([]byte, 2*externalapi.DomainHashSize+1, 2*externalapi.DomainHashSize+1+10)
|
||||
buf := make([]byte, 2*daghash.HashSize+1, 2*daghash.HashSize+1+10)
|
||||
copy(buf, o.TxID.String())
|
||||
buf[2*externalapi.DomainHashSize] = ':'
|
||||
buf[2*daghash.HashSize] = ':'
|
||||
buf = strconv.AppendUint(buf, uint64(o.Index), 10)
|
||||
return string(buf)
|
||||
}
|
||||
@@ -96,6 +205,27 @@ type TxIn struct {
|
||||
Sequence uint64
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize the
|
||||
// the transaction input.
|
||||
func (t *TxIn) SerializeSize() int {
|
||||
return t.serializeSize(txEncodingFull)
|
||||
}
|
||||
|
||||
func (t *TxIn) serializeSize(encodingFlags txEncoding) int {
|
||||
// Outpoint ID 32 bytes + Outpoint Index 4 bytes + Sequence 8 bytes +
|
||||
// serialized varint size for the length of SignatureScript +
|
||||
// SignatureScript bytes.
|
||||
return 44 + serializeSignatureScriptSize(t.SignatureScript, encodingFlags)
|
||||
}
|
||||
|
||||
func serializeSignatureScriptSize(signatureScript []byte, encodingFlags txEncoding) int {
|
||||
if encodingFlags&txEncodingExcludeSignatureScript != txEncodingExcludeSignatureScript {
|
||||
return VarIntSerializeSize(uint64(len(signatureScript))) +
|
||||
len(signatureScript)
|
||||
}
|
||||
return VarIntSerializeSize(0)
|
||||
}
|
||||
|
||||
// NewTxIn returns a new kaspa transaction input with the provided
|
||||
// previous outpoint point and signature script with a default sequence of
|
||||
// MaxTxInSequenceNum.
|
||||
@@ -103,7 +233,7 @@ func NewTxIn(prevOut *Outpoint, signatureScript []byte) *TxIn {
|
||||
return &TxIn{
|
||||
PreviousOutpoint: *prevOut,
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
Sequence: MaxTxInSequenceNum,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +243,14 @@ type TxOut struct {
|
||||
ScriptPubKey []byte
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize the
|
||||
// the transaction output.
|
||||
func (t *TxOut) SerializeSize() int {
|
||||
// Value 8 bytes + serialized varint size for the length of ScriptPubKey +
|
||||
// ScriptPubKey bytes.
|
||||
return 8 + VarIntSerializeSize(uint64(len(t.ScriptPubKey))) + len(t.ScriptPubKey)
|
||||
}
|
||||
|
||||
// NewTxOut returns a new kaspa transaction output with the provided
|
||||
// transaction value and public key script.
|
||||
func NewTxOut(value uint64, scriptPubKey []byte) *TxOut {
|
||||
@@ -134,9 +272,9 @@ type MsgTx struct {
|
||||
TxIn []*TxIn
|
||||
TxOut []*TxOut
|
||||
LockTime uint64
|
||||
SubnetworkID externalapi.DomainSubnetworkID
|
||||
SubnetworkID subnetworkid.SubnetworkID
|
||||
Gas uint64
|
||||
PayloadHash externalapi.DomainHash
|
||||
PayloadHash *daghash.Hash
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
@@ -157,17 +295,41 @@ func (msg *MsgTx) AddTxOut(to *TxOut) {
|
||||
// value and reference the relevant block id, instead of previous transaction id.
|
||||
func (msg *MsgTx) IsCoinBase() bool {
|
||||
// A coinbase transaction must have subnetwork id SubnetworkIDCoinbase
|
||||
return msg.SubnetworkID == subnetworks.SubnetworkIDCoinbase
|
||||
return msg.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDCoinbase)
|
||||
}
|
||||
|
||||
// TxHash generates the Hash for the transaction.
|
||||
func (msg *MsgTx) TxHash() *externalapi.DomainHash {
|
||||
return consensusserialization.TransactionHash(MsgTxToDomainTransaction(msg))
|
||||
func (msg *MsgTx) TxHash() *daghash.Hash {
|
||||
// Encode the transaction and calculate double sha256 on the result.
|
||||
writer := daghash.NewDoubleHashWriter()
|
||||
err := msg.serialize(writer, txEncodingExcludePayload)
|
||||
if err != nil {
|
||||
// this writer never return errors (no allocations or possible failures) so errors can only come from validity checks,
|
||||
// and we assume we never construct malformed transactions.
|
||||
panic(fmt.Sprintf("TxHash() failed. this should never fail for structurally-valid transactions. err: %+v", err))
|
||||
}
|
||||
|
||||
hash := writer.Finalize()
|
||||
return &hash
|
||||
}
|
||||
|
||||
// TxID generates the Hash for the transaction without the signature script, gas and payload fields.
|
||||
func (msg *MsgTx) TxID() *externalapi.DomainTransactionID {
|
||||
return consensusserialization.TransactionID(MsgTxToDomainTransaction(msg))
|
||||
func (msg *MsgTx) TxID() *daghash.TxID {
|
||||
// Encode the transaction, replace signature script with zeroes, cut off
|
||||
// payload and calculate double sha256 on the result.
|
||||
var encodingFlags txEncoding
|
||||
if !msg.IsCoinBase() {
|
||||
encodingFlags = txEncodingExcludeSignatureScript | txEncodingExcludePayload
|
||||
}
|
||||
writer := daghash.NewDoubleHashWriter()
|
||||
err := msg.serialize(writer, encodingFlags)
|
||||
if err != nil {
|
||||
// this writer never return errors (no allocations or possible failures) so errors can only come from validity checks,
|
||||
// and we assume we never construct malformed transactions.
|
||||
panic(fmt.Sprintf("TxID() failed. this should never fail for structurally-valid transactions. err: %+v", err))
|
||||
}
|
||||
txID := daghash.TxID(writer.Finalize())
|
||||
return &txID
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of a transaction so that the original does not get
|
||||
@@ -195,7 +357,7 @@ func (msg *MsgTx) Copy() *MsgTx {
|
||||
// Deep copy the old previous outpoint.
|
||||
oldOutpoint := oldTxIn.PreviousOutpoint
|
||||
newOutpoint := Outpoint{}
|
||||
newOutpoint.TxID = oldOutpoint.TxID
|
||||
newOutpoint.TxID.SetBytes(oldOutpoint.TxID[:])
|
||||
newOutpoint.Index = oldOutpoint.Index
|
||||
|
||||
// Deep copy the old signature script.
|
||||
@@ -241,6 +403,368 @@ func (msg *MsgTx) Copy() *MsgTx {
|
||||
return &newTx
|
||||
}
|
||||
|
||||
// KaspaDecode decodes r using the kaspa protocol encoding into the receiver.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Deserialize for decoding transactions stored to disk, such as in a
|
||||
// database, as opposed to decoding transactions from the appmessage.
|
||||
func (msg *MsgTx) KaspaDecode(r io.Reader, pver uint32) error {
|
||||
version, err := binaryserializer.Uint32(r, littleEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg.Version = int32(version)
|
||||
|
||||
count, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prevent more input transactions than could possibly fit into a
|
||||
// message. It would be possible to cause memory exhaustion and panics
|
||||
// without a sane upper bound on this count.
|
||||
if count > uint64(maxTxInPerMessage) {
|
||||
str := fmt.Sprintf("too many input transactions to fit into "+
|
||||
"max message size [count %d, max %d]", count,
|
||||
maxTxInPerMessage)
|
||||
return messageError("MsgTx.KaspaDecode", str)
|
||||
}
|
||||
|
||||
// returnScriptBuffers is a closure that returns any script buffers that
|
||||
// were borrowed from the pool when there are any deserialization
|
||||
// errors. This is only valid to call before the final step which
|
||||
// replaces the scripts with the location in a contiguous buffer and
|
||||
// returns them.
|
||||
returnScriptBuffers := func() {
|
||||
for _, txIn := range msg.TxIn {
|
||||
if txIn == nil || txIn.SignatureScript == nil {
|
||||
continue
|
||||
}
|
||||
scriptPool.Return(txIn.SignatureScript)
|
||||
}
|
||||
for _, txOut := range msg.TxOut {
|
||||
if txOut == nil || txOut.ScriptPubKey == nil {
|
||||
continue
|
||||
}
|
||||
scriptPool.Return(txOut.ScriptPubKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize the inputs.
|
||||
var totalScriptSize uint64
|
||||
txIns := make([]TxIn, count)
|
||||
msg.TxIn = make([]*TxIn, count)
|
||||
for i := uint64(0); i < count; i++ {
|
||||
// The pointer is set now in case a script buffer is borrowed
|
||||
// and needs to be returned to the pool on error.
|
||||
ti := &txIns[i]
|
||||
msg.TxIn[i] = ti
|
||||
err = readTxIn(r, pver, msg.Version, ti)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
totalScriptSize += uint64(len(ti.SignatureScript))
|
||||
}
|
||||
|
||||
count, err = ReadVarInt(r)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
// Prevent more output transactions than could possibly fit into a
|
||||
// message. It would be possible to cause memory exhaustion and panics
|
||||
// without a sane upper bound on this count.
|
||||
if count > uint64(maxTxOutPerMessage) {
|
||||
returnScriptBuffers()
|
||||
str := fmt.Sprintf("too many output transactions to fit into "+
|
||||
"max message size [count %d, max %d]", count,
|
||||
maxTxOutPerMessage)
|
||||
return messageError("MsgTx.KaspaDecode", str)
|
||||
}
|
||||
|
||||
// Deserialize the outputs.
|
||||
txOuts := make([]TxOut, count)
|
||||
msg.TxOut = make([]*TxOut, count)
|
||||
for i := uint64(0); i < count; i++ {
|
||||
// The pointer is set now in case a script buffer is borrowed
|
||||
// and needs to be returned to the pool on error.
|
||||
to := &txOuts[i]
|
||||
msg.TxOut[i] = to
|
||||
err = readTxOut(r, pver, msg.Version, to)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
totalScriptSize += uint64(len(to.ScriptPubKey))
|
||||
}
|
||||
|
||||
lockTime, err := binaryserializer.Uint64(r, littleEndian)
|
||||
msg.LockTime = lockTime
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(r, msg.SubnetworkID[:])
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
if !msg.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
|
||||
msg.Gas, err = binaryserializer.Uint64(r, littleEndian)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
var payloadHash daghash.Hash
|
||||
err = ReadElement(r, &payloadHash)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
msg.PayloadHash = &payloadHash
|
||||
|
||||
payloadLength, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
msg.Payload = make([]byte, payloadLength)
|
||||
_, err = io.ReadFull(r, msg.Payload)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create a single allocation to house all of the scripts and set each
|
||||
// input signature script and output public key script to the
|
||||
// appropriate subslice of the overall contiguous buffer. Then, return
|
||||
// each individual script buffer back to the pool so they can be reused
|
||||
// for future deserializations. This is done because it significantly
|
||||
// reduces the number of allocations the garbage collector needs to
|
||||
// track, which in turn improves performance and drastically reduces the
|
||||
// amount of runtime overhead that would otherwise be needed to keep
|
||||
// track of millions of small allocations.
|
||||
//
|
||||
// NOTE: It is no longer valid to call the returnScriptBuffers closure
|
||||
// after these blocks of code run because it is already done and the
|
||||
// scripts in the transaction inputs and outputs no longer point to the
|
||||
// buffers.
|
||||
var offset uint64
|
||||
scripts := make([]byte, totalScriptSize)
|
||||
for i := 0; i < len(msg.TxIn); i++ {
|
||||
// Copy the signature script into the contiguous buffer at the
|
||||
// appropriate offset.
|
||||
signatureScript := msg.TxIn[i].SignatureScript
|
||||
copy(scripts[offset:], signatureScript)
|
||||
|
||||
// Reset the signature script of the transaction input to the
|
||||
// slice of the contiguous buffer where the script lives.
|
||||
scriptSize := uint64(len(signatureScript))
|
||||
end := offset + scriptSize
|
||||
msg.TxIn[i].SignatureScript = scripts[offset:end:end]
|
||||
offset += scriptSize
|
||||
|
||||
// Return the temporary script buffer to the pool.
|
||||
scriptPool.Return(signatureScript)
|
||||
}
|
||||
for i := 0; i < len(msg.TxOut); i++ {
|
||||
// Copy the public key script into the contiguous buffer at the
|
||||
// appropriate offset.
|
||||
scriptPubKey := msg.TxOut[i].ScriptPubKey
|
||||
copy(scripts[offset:], scriptPubKey)
|
||||
|
||||
// Reset the public key script of the transaction output to the
|
||||
// slice of the contiguous buffer where the script lives.
|
||||
scriptSize := uint64(len(scriptPubKey))
|
||||
end := offset + scriptSize
|
||||
msg.TxOut[i].ScriptPubKey = scripts[offset:end:end]
|
||||
offset += scriptSize
|
||||
|
||||
// Return the temporary script buffer to the pool.
|
||||
scriptPool.Return(scriptPubKey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deserialize decodes a transaction from r into the receiver using a format
|
||||
// that is suitable for long-term storage such as a database while respecting
|
||||
// the Version field in the transaction. This function differs from KaspaDecode
|
||||
// in that KaspaDecode decodes from the kaspa appmessage protocol as it was sent
|
||||
// across the network. The appmessage encoding can technically differ depending on
|
||||
// the protocol version and doesn't even really need to match the format of a
|
||||
// stored transaction at all. As of the time this comment was written, the
|
||||
// encoded transaction is the same in both instances, but there is a distinct
|
||||
// difference and separating the two allows the API to be flexible enough to
|
||||
// deal with changes.
|
||||
func (msg *MsgTx) Deserialize(r io.Reader) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of KaspaDecode.
|
||||
return msg.KaspaDecode(r, 0)
|
||||
}
|
||||
|
||||
// KaspaEncode encodes the receiver to w using the kaspa protocol encoding.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Serialize for encoding transactions to be stored to disk, such as in a
|
||||
// database, as opposed to encoding transactions for the appmessage.
|
||||
func (msg *MsgTx) KaspaEncode(w io.Writer, pver uint32) error {
|
||||
return msg.encode(w, pver, txEncodingFull)
|
||||
}
|
||||
|
||||
func (msg *MsgTx) encode(w io.Writer, pver uint32, encodingFlags txEncoding) error {
|
||||
err := binaryserializer.PutUint32(w, littleEndian, uint32(msg.Version))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count := uint64(len(msg.TxIn))
|
||||
err = WriteVarInt(w, count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ti := range msg.TxIn {
|
||||
err = writeTxIn(w, pver, msg.Version, ti, encodingFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
count = uint64(len(msg.TxOut))
|
||||
err = WriteVarInt(w, count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, to := range msg.TxOut {
|
||||
err = WriteTxOut(w, pver, msg.Version, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = binaryserializer.PutUint64(w, littleEndian, msg.LockTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(msg.SubnetworkID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !msg.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
|
||||
if msg.SubnetworkID.IsBuiltIn() && msg.Gas != 0 {
|
||||
str := "Transactions from built-in should have 0 gas"
|
||||
return messageError("MsgTx.KaspaEncode", str)
|
||||
}
|
||||
|
||||
err = binaryserializer.PutUint64(w, littleEndian, msg.Gas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = WriteElement(w, msg.PayloadHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if encodingFlags&txEncodingExcludePayload != txEncodingExcludePayload {
|
||||
err = WriteVarInt(w, uint64(len(msg.Payload)))
|
||||
w.Write(msg.Payload)
|
||||
} else {
|
||||
err = WriteVarInt(w, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if msg.Payload != nil {
|
||||
str := "Transactions from native subnetwork should have <nil> payload"
|
||||
return messageError("MsgTx.KaspaEncode", str)
|
||||
} else if msg.PayloadHash != nil {
|
||||
str := "Transactions from native subnetwork should have <nil> payload hash"
|
||||
return messageError("MsgTx.KaspaEncode", str)
|
||||
} else if msg.Gas != 0 {
|
||||
str := "Transactions from native subnetwork should have 0 gas"
|
||||
return messageError("MsgTx.KaspaEncode", str)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize encodes the transaction to w using a format that suitable for
|
||||
// long-term storage such as a database while respecting the Version field in
|
||||
// the transaction. This function differs from KaspaEncode in that KaspaEncode
|
||||
// encodes the transaction to the kaspa appmessage protocol in order to be sent
|
||||
// across the network. The appmessage encoding can technically differ depending on
|
||||
// the protocol version and doesn't even really need to match the format of a
|
||||
// stored transaction at all. As of the time this comment was written, the
|
||||
// encoded transaction is the same in both instances, but there is a distinct
|
||||
// difference and separating the two allows the API to be flexible enough to
|
||||
// deal with changes.
|
||||
func (msg *MsgTx) Serialize(w io.Writer) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of KaspaEncode.
|
||||
return msg.KaspaEncode(w, 0)
|
||||
}
|
||||
|
||||
func (msg *MsgTx) serialize(w io.Writer, encodingFlags txEncoding) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of `encode`.
|
||||
return msg.encode(w, 0, encodingFlags)
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize
|
||||
// the transaction.
|
||||
func (msg *MsgTx) SerializeSize() int {
|
||||
return msg.serializeSize(txEncodingFull)
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize
|
||||
// the transaction.
|
||||
func (msg *MsgTx) serializeSize(encodingFlags txEncoding) int {
|
||||
// Version 4 bytes + LockTime 8 bytes + SubnetworkID 20
|
||||
// bytes + Serialized varint size for the number of transaction
|
||||
// inputs and outputs.
|
||||
n := 32 + VarIntSerializeSize(uint64(len(msg.TxIn))) +
|
||||
VarIntSerializeSize(uint64(len(msg.TxOut)))
|
||||
|
||||
if !msg.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
|
||||
// Gas 8 bytes
|
||||
n += 8
|
||||
|
||||
// PayloadHash
|
||||
n += daghash.HashSize
|
||||
|
||||
// Serialized varint size for the length of the payload
|
||||
if encodingFlags&txEncodingExcludePayload != txEncodingExcludePayload {
|
||||
n += VarIntSerializeSize(uint64(len(msg.Payload)))
|
||||
n += len(msg.Payload)
|
||||
} else {
|
||||
n += VarIntSerializeSize(0)
|
||||
}
|
||||
}
|
||||
|
||||
for _, txIn := range msg.TxIn {
|
||||
n += txIn.serializeSize(encodingFlags)
|
||||
}
|
||||
|
||||
for _, txOut := range msg.TxOut {
|
||||
n += txOut.SerializeSize()
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgTx) Command() MessageCommand {
|
||||
@@ -253,14 +777,52 @@ func (msg *MsgTx) MaxPayloadLength(pver uint32) uint32 {
|
||||
return MaxMessagePayload
|
||||
}
|
||||
|
||||
// ScriptPubKeyLocs returns a slice containing the start of each public key script
|
||||
// within the raw serialized transaction. The caller can easily obtain the
|
||||
// length of each script by using len on the script available via the
|
||||
// appropriate transaction output entry.
|
||||
func (msg *MsgTx) ScriptPubKeyLocs() []int {
|
||||
numTxOut := len(msg.TxOut)
|
||||
if numTxOut == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The starting offset in the serialized transaction of the first
|
||||
// transaction output is:
|
||||
//
|
||||
// Version 4 bytes + serialized varint size for the number of
|
||||
// transaction inputs and outputs + serialized size of each transaction
|
||||
// input.
|
||||
n := 4 + VarIntSerializeSize(uint64(len(msg.TxIn))) +
|
||||
VarIntSerializeSize(uint64(numTxOut))
|
||||
|
||||
for _, txIn := range msg.TxIn {
|
||||
n += txIn.SerializeSize()
|
||||
}
|
||||
|
||||
// Calculate and set the appropriate offset for each public key script.
|
||||
scriptPubKeyLocs := make([]int, numTxOut)
|
||||
for i, txOut := range msg.TxOut {
|
||||
// The offset of the script in the transaction output is:
|
||||
//
|
||||
// Value 8 bytes + serialized varint size for the length of
|
||||
// ScriptPubKey.
|
||||
n += 8 + VarIntSerializeSize(uint64(len(txOut.ScriptPubKey)))
|
||||
scriptPubKeyLocs[i] = n
|
||||
n += len(txOut.ScriptPubKey)
|
||||
}
|
||||
|
||||
return scriptPubKeyLocs
|
||||
}
|
||||
|
||||
// IsSubnetworkCompatible return true iff subnetworkID is one or more of the following:
|
||||
// 1. The SupportsAll subnetwork (full node)
|
||||
// 2. The native subnetwork
|
||||
// 3. The transaction's subnetwork
|
||||
func (msg *MsgTx) IsSubnetworkCompatible(subnetworkID *externalapi.DomainSubnetworkID) bool {
|
||||
func (msg *MsgTx) IsSubnetworkCompatible(subnetworkID *subnetworkid.SubnetworkID) bool {
|
||||
return subnetworkID == nil ||
|
||||
*subnetworkID == subnetworks.SubnetworkIDNative ||
|
||||
*subnetworkID == msg.SubnetworkID
|
||||
subnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) ||
|
||||
subnetworkID.IsEqual(&msg.SubnetworkID)
|
||||
}
|
||||
|
||||
// newMsgTx returns a new tx message that conforms to the Message interface.
|
||||
@@ -272,7 +834,7 @@ func (msg *MsgTx) IsSubnetworkCompatible(subnetworkID *externalapi.DomainSubnetw
|
||||
// The payload hash is calculated automatically according to provided payload.
|
||||
// Also, the lock time is set to zero to indicate the transaction is valid
|
||||
// immediately as opposed to some time in future.
|
||||
func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externalapi.DomainSubnetworkID,
|
||||
func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *subnetworkid.SubnetworkID,
|
||||
gas uint64, payload []byte, lockTime uint64) *MsgTx {
|
||||
|
||||
if txIn == nil {
|
||||
@@ -283,9 +845,9 @@ func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externa
|
||||
txOut = make([]*TxOut, 0, defaultTxInOutAlloc)
|
||||
}
|
||||
|
||||
var payloadHash externalapi.DomainHash
|
||||
if *subnetworkID != subnetworks.SubnetworkIDNative {
|
||||
payloadHash = *hashes.HashData(payload)
|
||||
var payloadHash *daghash.Hash
|
||||
if !subnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
|
||||
payloadHash = daghash.DoubleHashP(payload)
|
||||
}
|
||||
|
||||
return &MsgTx{
|
||||
@@ -302,11 +864,11 @@ func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externa
|
||||
|
||||
// NewNativeMsgTx returns a new tx message in the native subnetwork
|
||||
func NewNativeMsgTx(version int32, txIn []*TxIn, txOut []*TxOut) *MsgTx {
|
||||
return newMsgTx(version, txIn, txOut, &subnetworks.SubnetworkIDNative, 0, nil, 0)
|
||||
return newMsgTx(version, txIn, txOut, subnetworkid.SubnetworkIDNative, 0, nil, 0)
|
||||
}
|
||||
|
||||
// NewSubnetworkMsgTx returns a new tx message in the specified subnetwork with specified gas and payload
|
||||
func NewSubnetworkMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externalapi.DomainSubnetworkID,
|
||||
func NewSubnetworkMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *subnetworkid.SubnetworkID,
|
||||
gas uint64, payload []byte) *MsgTx {
|
||||
|
||||
return newMsgTx(version, txIn, txOut, subnetworkID, gas, payload, 0)
|
||||
@@ -316,7 +878,7 @@ func NewSubnetworkMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkI
|
||||
//
|
||||
// See newMsgTx for further documntation of the parameters
|
||||
func NewNativeMsgTxWithLocktime(version int32, txIn []*TxIn, txOut []*TxOut, locktime uint64) *MsgTx {
|
||||
return newMsgTx(version, txIn, txOut, &subnetworks.SubnetworkIDNative, 0, nil, locktime)
|
||||
return newMsgTx(version, txIn, txOut, subnetworkid.SubnetworkIDNative, 0, nil, locktime)
|
||||
}
|
||||
|
||||
// NewRegistryMsgTx creates a new MsgTx that registers a new subnetwork
|
||||
@@ -324,5 +886,119 @@ func NewRegistryMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, gasLimit uint
|
||||
payload := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(payload, gasLimit)
|
||||
|
||||
return NewSubnetworkMsgTx(version, txIn, txOut, &subnetworks.SubnetworkIDRegistry, 0, payload)
|
||||
return NewSubnetworkMsgTx(version, txIn, txOut, subnetworkid.SubnetworkIDRegistry, 0, payload)
|
||||
}
|
||||
|
||||
// readOutpoint reads the next sequence of bytes from r as an Outpoint.
|
||||
func readOutpoint(r io.Reader, pver uint32, version int32, op *Outpoint) error {
|
||||
_, err := io.ReadFull(r, op.TxID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op.Index, err = binaryserializer.Uint32(r, littleEndian)
|
||||
return err
|
||||
}
|
||||
|
||||
// writeOutpoint encodes op to the kaspa protocol encoding for an Outpoint
|
||||
// to w.
|
||||
func writeOutpoint(w io.Writer, pver uint32, version int32, op *Outpoint) error {
|
||||
_, err := w.Write(op.TxID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return binaryserializer.PutUint32(w, littleEndian, op.Index)
|
||||
}
|
||||
|
||||
// readScript reads a variable length byte array that represents a transaction
|
||||
// script. It is encoded as a varInt containing the length of the array
|
||||
// followed by the bytes themselves. An error is returned if the length is
|
||||
// greater than the passed maxAllowed parameter which helps protect against
|
||||
// memory exhaustion attacks and forced panics through malformed messages. The
|
||||
// fieldName parameter is only used for the error message so it provides more
|
||||
// context in the error.
|
||||
func readScript(r io.Reader, pver uint32, maxAllowed uint32, fieldName string) ([]byte, error) {
|
||||
count, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prevent byte array larger than the max message size. It would
|
||||
// be possible to cause memory exhaustion and panics without a sane
|
||||
// upper bound on this count.
|
||||
if count > uint64(maxAllowed) {
|
||||
str := fmt.Sprintf("%s is larger than the max allowed size "+
|
||||
"[count %d, max %d]", fieldName, count, maxAllowed)
|
||||
return nil, messageError("readScript", str)
|
||||
}
|
||||
|
||||
b := scriptPool.Borrow(count)
|
||||
_, err = io.ReadFull(r, b)
|
||||
if err != nil {
|
||||
scriptPool.Return(b)
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// readTxIn reads the next sequence of bytes from r as a transaction input
|
||||
// (TxIn).
|
||||
func readTxIn(r io.Reader, pver uint32, version int32, ti *TxIn) error {
|
||||
err := readOutpoint(r, pver, version, &ti.PreviousOutpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ti.SignatureScript, err = readScript(r, pver, MaxMessagePayload,
|
||||
"transaction input signature script")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ReadElement(r, &ti.Sequence)
|
||||
}
|
||||
|
||||
// writeTxIn encodes ti to the kaspa protocol encoding for a transaction
|
||||
// input (TxIn) to w.
|
||||
func writeTxIn(w io.Writer, pver uint32, version int32, ti *TxIn, encodingFlags txEncoding) error {
|
||||
err := writeOutpoint(w, pver, version, &ti.PreviousOutpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if encodingFlags&txEncodingExcludeSignatureScript != txEncodingExcludeSignatureScript {
|
||||
err = WriteVarBytes(w, pver, ti.SignatureScript)
|
||||
} else {
|
||||
err = WriteVarBytes(w, pver, []byte{})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return binaryserializer.PutUint64(w, littleEndian, ti.Sequence)
|
||||
}
|
||||
|
||||
// readTxOut reads the next sequence of bytes from r as a transaction output
|
||||
// (TxOut).
|
||||
func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error {
|
||||
err := ReadElement(r, &to.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
to.ScriptPubKey, err = readScript(r, pver, MaxMessagePayload,
|
||||
"transaction output public key script")
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteTxOut encodes to into the kaspa protocol encoding for a transaction
|
||||
// output (TxOut) to w.
|
||||
func WriteTxOut(w io.Writer, pver uint32, version int32, to *TxOut) error {
|
||||
err := binaryserializer.PutUint64(w, littleEndian, uint64(to.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WriteVarBytes(w, pver, to.ScriptPubKey)
|
||||
}
|
||||
|
||||
@@ -7,17 +7,16 @@ package appmessage
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionid"
|
||||
"unsafe"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// TestTx tests the MsgTx API.
|
||||
@@ -25,7 +24,7 @@ func TestTx(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
txIDStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
txID, err := transactionid.FromString(txIDStr)
|
||||
txID, err := daghash.NewTxIDFromStr(txIDStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewTxIDFromStr: %v", err)
|
||||
}
|
||||
@@ -52,7 +51,7 @@ func TestTx(t *testing.T) {
|
||||
// testing package functionality.
|
||||
prevOutIndex := uint32(1)
|
||||
prevOut := NewOutpoint(txID, prevOutIndex)
|
||||
if prevOut.TxID != *txID {
|
||||
if !prevOut.TxID.IsEqual(txID) {
|
||||
t.Errorf("NewOutpoint: wrong ID - got %v, want %v",
|
||||
spew.Sprint(&prevOut.TxID), spew.Sprint(txID))
|
||||
}
|
||||
@@ -131,8 +130,8 @@ func TestTx(t *testing.T) {
|
||||
|
||||
// TestTxHash tests the ability to generate the hash of a transaction accurately.
|
||||
func TestTxHashAndID(t *testing.T) {
|
||||
txID1Str := "a3d29c39bfb578235e4813cc8138a9ba10def63acad193a7a880159624840d7f"
|
||||
wantTxID1, err := transactionid.FromString(txID1Str)
|
||||
txID1Str := "edca872f27279674c7a52192b32fd68b8b8be714bfea52d98b2c3c86c30e85c6"
|
||||
wantTxID1, err := daghash.NewTxIDFromStr(txID1Str)
|
||||
if err != nil {
|
||||
t.Errorf("NewTxIDFromStr: %v", err)
|
||||
return
|
||||
@@ -141,7 +140,7 @@ func TestTxHashAndID(t *testing.T) {
|
||||
// A coinbase transaction
|
||||
txIn := &TxIn{
|
||||
PreviousOutpoint: Outpoint{
|
||||
TxID: externalapi.DomainTransactionID{},
|
||||
TxID: daghash.TxID{},
|
||||
Index: math.MaxUint32,
|
||||
},
|
||||
SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62},
|
||||
@@ -163,31 +162,31 @@ func TestTxHashAndID(t *testing.T) {
|
||||
0xac, // OP_CHECKSIG
|
||||
},
|
||||
}
|
||||
tx1 := NewSubnetworkMsgTx(1, []*TxIn{txIn}, []*TxOut{txOut}, &subnetworks.SubnetworkIDCoinbase, 0, nil)
|
||||
tx1 := NewSubnetworkMsgTx(1, []*TxIn{txIn}, []*TxOut{txOut}, subnetworkid.SubnetworkIDCoinbase, 0, nil)
|
||||
|
||||
// Ensure the hash produced is expected.
|
||||
tx1Hash := tx1.TxHash()
|
||||
if *tx1Hash != (externalapi.DomainHash)(*wantTxID1) {
|
||||
if !tx1Hash.IsEqual((*daghash.Hash)(wantTxID1)) {
|
||||
t.Errorf("TxHash: wrong hash - got %v, want %v",
|
||||
spew.Sprint(tx1Hash), spew.Sprint(wantTxID1))
|
||||
}
|
||||
|
||||
// Ensure the TxID for coinbase transaction is the same as TxHash.
|
||||
tx1ID := tx1.TxID()
|
||||
if *tx1ID != *wantTxID1 {
|
||||
if !tx1ID.IsEqual(wantTxID1) {
|
||||
t.Errorf("TxID: wrong ID - got %v, want %v",
|
||||
spew.Sprint(tx1ID), spew.Sprint(wantTxID1))
|
||||
}
|
||||
|
||||
hash2Str := "c84f3009b337aaa3adeb2ffd41010d5f62dd773ca25b39c908a77da91f87b729"
|
||||
wantHash2, err := hashes.FromString(hash2Str)
|
||||
hash2Str := "b11924b7eeffea821522222576c53dc5b8ddd97602f81e5e124d2626646d74ca"
|
||||
wantHash2, err := daghash.NewHashFromStr(hash2Str)
|
||||
if err != nil {
|
||||
t.Errorf("NewTxIDFromStr: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
id2Str := "7c919f676109743a1271a88beeb43849a6f9cc653f6082e59a7266f3df4802b9"
|
||||
wantID2, err := transactionid.FromString(id2Str)
|
||||
id2Str := "750499ae9e6d44961ef8bad8af27a44dd4bcbea166b71baf181e8d3997e1ff72"
|
||||
wantID2, err := daghash.NewTxIDFromStr(id2Str)
|
||||
if err != nil {
|
||||
t.Errorf("NewTxIDFromStr: %v", err)
|
||||
return
|
||||
@@ -196,7 +195,7 @@ func TestTxHashAndID(t *testing.T) {
|
||||
txIns := []*TxIn{{
|
||||
PreviousOutpoint: Outpoint{
|
||||
Index: 0,
|
||||
TxID: externalapi.DomainTransactionID{1, 2, 3},
|
||||
TxID: daghash.TxID{1, 2, 3},
|
||||
},
|
||||
SignatureScript: []byte{
|
||||
0x49, 0x30, 0x46, 0x02, 0x21, 0x00, 0xDA, 0x0D, 0xC6, 0xAE, 0xCE, 0xFE, 0x1E, 0x06, 0xEF, 0xDF,
|
||||
@@ -227,29 +226,717 @@ func TestTxHashAndID(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
tx2 := NewSubnetworkMsgTx(1, txIns, txOuts, &externalapi.DomainSubnetworkID{1, 2, 3}, 0, payload)
|
||||
tx2 := NewSubnetworkMsgTx(1, txIns, txOuts, &subnetworkid.SubnetworkID{1, 2, 3}, 0, payload)
|
||||
|
||||
// Ensure the hash produced is expected.
|
||||
tx2Hash := tx2.TxHash()
|
||||
if *tx2Hash != *wantHash2 {
|
||||
if !tx2Hash.IsEqual(wantHash2) {
|
||||
t.Errorf("TxHash: wrong hash - got %v, want %v",
|
||||
spew.Sprint(tx2Hash), spew.Sprint(wantHash2))
|
||||
}
|
||||
|
||||
// Ensure the TxID for coinbase transaction is the same as TxHash.
|
||||
tx2ID := tx2.TxID()
|
||||
if *tx2ID != *wantID2 {
|
||||
if !tx2ID.IsEqual(wantID2) {
|
||||
t.Errorf("TxID: wrong ID - got %v, want %v",
|
||||
spew.Sprint(tx2ID), spew.Sprint(wantID2))
|
||||
}
|
||||
|
||||
if *tx2ID == (externalapi.DomainTransactionID)(*tx2Hash) {
|
||||
if tx2ID.IsEqual((*daghash.TxID)(tx2Hash)) {
|
||||
t.Errorf("tx2ID and tx2Hash shouldn't be the same for non-coinbase transaction with signature and/or payload")
|
||||
}
|
||||
|
||||
tx2.TxIn[0].SignatureScript = []byte{}
|
||||
newTx2Hash := tx2.TxHash()
|
||||
if *tx2ID != (externalapi.DomainTransactionID)(*newTx2Hash) {
|
||||
if !tx2ID.IsEqual((*daghash.TxID)(newTx2Hash)) {
|
||||
t.Errorf("tx2ID and newTx2Hash should be the same for transaction with an empty signature")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxEncoding tests the MsgTx appmessage encode and decode for various numbers
|
||||
// of transaction inputs and outputs and protocol versions.
|
||||
func TestTxEncoding(t *testing.T) {
|
||||
// Empty tx message.
|
||||
noTx := NewNativeMsgTx(1, nil, nil)
|
||||
noTxEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0x00, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *MsgTx // Message to encode
|
||||
out *MsgTx // Expected decoded message
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version with no transactions.
|
||||
{
|
||||
noTx,
|
||||
noTx,
|
||||
noTxEncoded,
|
||||
ProtocolVersion,
|
||||
},
|
||||
|
||||
// Latest protocol version with multiple transactions.
|
||||
{
|
||||
multiTx,
|
||||
multiTx,
|
||||
multiTxEncoded,
|
||||
ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode the message to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.KaspaEncode(&buf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the message from appmessage format.
|
||||
var msg MsgTx
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = msg.KaspaDecode(rbuf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&msg, test.out) {
|
||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxEncodingErrors performs negative tests against appmessage encode and decode
|
||||
// of MsgTx to confirm error paths work correctly.
|
||||
func TestTxEncodingErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
tests := []struct {
|
||||
in *MsgTx // Value to encode
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Force error in version.
|
||||
{multiTx, multiTxEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error in number of transaction inputs.
|
||||
{multiTx, multiTxEncoded, pver, 4, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input previous block hash.
|
||||
{multiTx, multiTxEncoded, pver, 5, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input previous block output index.
|
||||
{multiTx, multiTxEncoded, pver, 37, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input signature script length.
|
||||
{multiTx, multiTxEncoded, pver, 41, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input signature script.
|
||||
{multiTx, multiTxEncoded, pver, 42, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input sequence.
|
||||
{multiTx, multiTxEncoded, pver, 49, io.ErrShortWrite, io.EOF},
|
||||
// Force error in number of transaction outputs.
|
||||
{multiTx, multiTxEncoded, pver, 57, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output value.
|
||||
{multiTx, multiTxEncoded, pver, 58, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output scriptPubKey length.
|
||||
{multiTx, multiTxEncoded, pver, 66, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output scriptPubKey.
|
||||
{multiTx, multiTxEncoded, pver, 67, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output lock time.
|
||||
{multiTx, multiTxEncoded, pver, 210, io.ErrShortWrite, io.EOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
var msg MsgTx
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxSerialize tests MsgTx serialize and deserialize.
|
||||
func TestTxSerialize(t *testing.T) {
|
||||
noTx := NewNativeMsgTx(1, nil, nil)
|
||||
noTxEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0x00, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
}
|
||||
|
||||
registryTx := NewRegistryMsgTx(1, nil, nil, 16)
|
||||
registryTxEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0x00, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Gas
|
||||
0x77, 0x56, 0x36, 0xb4, 0x89, 0x32, 0xe9, 0xa8,
|
||||
0xbb, 0x67, 0xe6, 0x54, 0x84, 0x36, 0x93, 0x8d,
|
||||
0x9f, 0xc5, 0x62, 0x49, 0x79, 0x5c, 0x0d, 0x0a,
|
||||
0x86, 0xaf, 0x7c, 0x5d, 0x54, 0x45, 0x4c, 0x4b, // Payload hash
|
||||
0x08, // Payload length varint
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Payload / Gas limit
|
||||
}
|
||||
|
||||
subnetworkTx := NewSubnetworkMsgTx(1, nil, nil, &subnetworkid.SubnetworkID{0xff}, 5, []byte{0, 1, 2})
|
||||
|
||||
subnetworkTxEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0x00, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Gas
|
||||
0x35, 0xf9, 0xf2, 0x93, 0x0e, 0xa3, 0x44, 0x61,
|
||||
0x88, 0x22, 0x79, 0x5e, 0xee, 0xc5, 0x68, 0xae,
|
||||
0x67, 0xab, 0x29, 0x87, 0xd8, 0xb1, 0x9e, 0x45,
|
||||
0x91, 0xe1, 0x05, 0x27, 0xba, 0xa1, 0xdf, 0x3d, // Payload hash
|
||||
0x03, // Payload length varint
|
||||
0x00, 0x01, 0x02, // Payload
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in *MsgTx // Message to encode
|
||||
out *MsgTx // Expected decoded message
|
||||
buf []byte // Serialized data
|
||||
scriptPubKeyLocs []int // Expected output script locations
|
||||
}{
|
||||
// No transactions.
|
||||
{
|
||||
"noTx",
|
||||
noTx,
|
||||
noTx,
|
||||
noTxEncoded,
|
||||
nil,
|
||||
},
|
||||
|
||||
// Registry Transaction.
|
||||
{
|
||||
"registryTx",
|
||||
registryTx,
|
||||
registryTx,
|
||||
registryTxEncoded,
|
||||
nil,
|
||||
},
|
||||
|
||||
// Sub Network Transaction.
|
||||
{
|
||||
"subnetworkTx",
|
||||
subnetworkTx,
|
||||
subnetworkTx,
|
||||
subnetworkTxEncoded,
|
||||
nil,
|
||||
},
|
||||
|
||||
// Multiple transactions.
|
||||
{
|
||||
"multiTx",
|
||||
multiTx,
|
||||
multiTx,
|
||||
multiTxEncoded,
|
||||
multiTxScriptPubKeyLocs,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the transaction.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Errorf("Serialize %s: error %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("Serialize %s:\n got: %s want: %s", test.name,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the transaction.
|
||||
var tx MsgTx
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = tx.Deserialize(rbuf)
|
||||
if err != nil {
|
||||
t.Errorf("Deserialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&tx, test.out) {
|
||||
t.Errorf("Deserialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&tx), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the public key script locations are accurate.
|
||||
scriptPubKeyLocs := test.in.ScriptPubKeyLocs()
|
||||
if !reflect.DeepEqual(scriptPubKeyLocs, test.scriptPubKeyLocs) {
|
||||
t.Errorf("ScriptPubKeyLocs #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(scriptPubKeyLocs),
|
||||
spew.Sdump(test.scriptPubKeyLocs))
|
||||
continue
|
||||
}
|
||||
for j, loc := range scriptPubKeyLocs {
|
||||
wantScriptPubKey := test.in.TxOut[j].ScriptPubKey
|
||||
gotScriptPubKey := test.buf[loc : loc+len(wantScriptPubKey)]
|
||||
if !bytes.Equal(gotScriptPubKey, wantScriptPubKey) {
|
||||
t.Errorf("ScriptPubKeyLocs #%d:%d\n unexpected "+
|
||||
"script got: %s want: %s", i, j,
|
||||
spew.Sdump(gotScriptPubKey),
|
||||
spew.Sdump(wantScriptPubKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxSerializeErrors performs negative tests against appmessage encode and decode
|
||||
// of MsgTx to confirm error paths work correctly.
|
||||
func TestTxSerializeErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgTx // Value to encode
|
||||
buf []byte // Serialized data
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Force error in version.
|
||||
{multiTx, multiTxEncoded, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error in number of transaction inputs.
|
||||
{multiTx, multiTxEncoded, 4, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input previous block hash.
|
||||
{multiTx, multiTxEncoded, 5, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input previous block output index.
|
||||
{multiTx, multiTxEncoded, 37, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input signature script length.
|
||||
{multiTx, multiTxEncoded, 41, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input signature script.
|
||||
{multiTx, multiTxEncoded, 42, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input sequence.
|
||||
{multiTx, multiTxEncoded, 49, io.ErrShortWrite, io.EOF},
|
||||
// Force error in number of transaction outputs.
|
||||
{multiTx, multiTxEncoded, 57, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output value.
|
||||
{multiTx, multiTxEncoded, 58, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output scriptPubKey length.
|
||||
{multiTx, multiTxEncoded, 66, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output scriptPubKey.
|
||||
{multiTx, multiTxEncoded, 67, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output lock time.
|
||||
{multiTx, multiTxEncoded, 210, io.ErrShortWrite, io.EOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the transaction.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.Serialize(w)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("Serialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the transaction.
|
||||
var tx MsgTx
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = tx.Deserialize(r)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
registryTx := NewSubnetworkMsgTx(1, nil, nil, subnetworkid.SubnetworkIDRegistry, 1, nil)
|
||||
|
||||
w := bytes.NewBuffer(make([]byte, 0, registryTx.SerializeSize()))
|
||||
err := registryTx.Serialize(w)
|
||||
str := "Transactions from built-in should have 0 gas"
|
||||
expectedErr := messageError("MsgTx.KaspaEncode", str)
|
||||
if err == nil || err.Error() != expectedErr.Error() {
|
||||
t.Errorf("TestTxSerializeErrors: expected error %v but got %v", expectedErr, err)
|
||||
}
|
||||
|
||||
nativeTx := NewSubnetworkMsgTx(1, nil, nil, subnetworkid.SubnetworkIDNative, 1, nil)
|
||||
w = bytes.NewBuffer(make([]byte, 0, registryTx.SerializeSize()))
|
||||
err = nativeTx.Serialize(w)
|
||||
|
||||
str = "Transactions from native subnetwork should have 0 gas"
|
||||
expectedErr = messageError("MsgTx.KaspaEncode", str)
|
||||
if err == nil || err.Error() != expectedErr.Error() {
|
||||
t.Errorf("TestTxSerializeErrors: expected error %v but got %v", expectedErr, err)
|
||||
}
|
||||
|
||||
nativeTx.Gas = 0
|
||||
nativeTx.Payload = []byte{1, 2, 3}
|
||||
nativeTx.PayloadHash = daghash.DoubleHashP(nativeTx.Payload)
|
||||
w = bytes.NewBuffer(make([]byte, 0, registryTx.SerializeSize()))
|
||||
err = nativeTx.Serialize(w)
|
||||
|
||||
str = "Transactions from native subnetwork should have <nil> payload"
|
||||
expectedErr = messageError("MsgTx.KaspaEncode", str)
|
||||
if err == nil || err.Error() != expectedErr.Error() {
|
||||
t.Errorf("TestTxSerializeErrors: expected error %v but got %v", expectedErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxOverflowErrors performs tests to ensure deserializing transactions
|
||||
// which are intentionally crafted to use large values for the variable number
|
||||
// of inputs and outputs are handled properly. This could otherwise potentially
|
||||
// be used as an attack vector.
|
||||
func TestTxOverflowErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
txVer := uint32(1)
|
||||
|
||||
tests := []struct {
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
version uint32 // Transaction version
|
||||
err error // Expected error
|
||||
}{
|
||||
// Transaction that claims to have ~uint64(0) inputs.
|
||||
{
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01, // Version
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // Varint for number of input transactions
|
||||
}, pver, txVer, &MessageError{},
|
||||
},
|
||||
|
||||
// Transaction that claims to have ~uint64(0) outputs.
|
||||
{
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // Varint for number of output transactions
|
||||
}, pver, txVer, &MessageError{},
|
||||
},
|
||||
|
||||
// Transaction that has an input with a signature script that
|
||||
// claims to have ~uint64(0) length.
|
||||
{
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01, // Version
|
||||
0x01, // Varint for number of input transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // Varint for length of signature script
|
||||
}, pver, txVer, &MessageError{},
|
||||
},
|
||||
|
||||
// Transaction that has an output with a public key script
|
||||
// that claims to have ~uint64(0) length.
|
||||
{
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01, // Version
|
||||
0x01, // Varint for number of input transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||
0x00, // Varint for length of signature script
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence
|
||||
0x01, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // Varint for length of public key script
|
||||
}, pver, txVer, &MessageError{},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Decode from appmessage format.
|
||||
var msg MsgTx
|
||||
r := bytes.NewReader(test.buf)
|
||||
err := msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
r = bytes.NewReader(test.buf)
|
||||
err = msg.Deserialize(r)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxSerializeSize performs tests to ensure the serialize size for
|
||||
// various transactions is accurate.
|
||||
func TestTxSerializeSize(t *testing.T) {
|
||||
// Empty tx message.
|
||||
noTx := NewNativeMsgTx(1, nil, nil)
|
||||
|
||||
tests := []struct {
|
||||
in *MsgTx // Tx to encode
|
||||
size int // Expected serialized size
|
||||
}{
|
||||
// No inputs or outpus.
|
||||
{noTx, 34},
|
||||
|
||||
// Transcaction with an input and an output.
|
||||
{multiTx, 238},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
serializedSize := test.in.SerializeSize()
|
||||
if serializedSize != test.size {
|
||||
t.Errorf("MsgTx.SerializeSize: #%d got: %d, want: %d", i,
|
||||
serializedSize, test.size)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSubnetworkCompatible(t *testing.T) {
|
||||
testTx := NewSubnetworkMsgTx(1, nil, nil, &subnetworkid.SubnetworkID{123}, 0, []byte{})
|
||||
tests := []struct {
|
||||
name string
|
||||
subnetworkID *subnetworkid.SubnetworkID
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "Native subnetwork",
|
||||
subnetworkID: subnetworkid.SubnetworkIDNative,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "same subnetwork as test tx",
|
||||
subnetworkID: &subnetworkid.SubnetworkID{123},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "other subnetwork",
|
||||
subnetworkID: &subnetworkid.SubnetworkID{234},
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := testTx.IsSubnetworkCompatible(test.subnetworkID)
|
||||
if result != test.expectedResult {
|
||||
t.Errorf("IsSubnetworkCompatible got unexpected result in test '%s': "+
|
||||
"expected: %t, want: %t", test.name, test.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScriptFreeList(t *testing.T) {
|
||||
var list scriptFreeList = make(chan []byte, freeListMaxItems)
|
||||
|
||||
expectedCapacity := 512
|
||||
expectedLengthFirst := 12
|
||||
expectedLengthSecond := 13
|
||||
|
||||
first := list.Borrow(uint64(expectedLengthFirst))
|
||||
if cap(first) != expectedCapacity {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected capacity for first %d, but got %d",
|
||||
expectedCapacity, cap(first))
|
||||
}
|
||||
if len(first) != expectedLengthFirst {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected length for first %d, but got %d",
|
||||
expectedLengthFirst, len(first))
|
||||
}
|
||||
list.Return(first)
|
||||
|
||||
// Borrow again, and check that the underlying array is re-used for second
|
||||
second := list.Borrow(uint64(expectedLengthSecond))
|
||||
if cap(second) != expectedCapacity {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected capacity for second %d, but got %d",
|
||||
expectedCapacity, cap(second))
|
||||
}
|
||||
if len(second) != expectedLengthSecond {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected length for second %d, but got %d",
|
||||
expectedLengthSecond, len(second))
|
||||
}
|
||||
|
||||
firstArrayAddress := underlyingArrayAddress(first)
|
||||
secondArrayAddress := underlyingArrayAddress(second)
|
||||
|
||||
if firstArrayAddress != secondArrayAddress {
|
||||
t.Errorf("First underlying array is at address %d and second at address %d, "+
|
||||
"which means memory was not re-used", firstArrayAddress, secondArrayAddress)
|
||||
}
|
||||
|
||||
list.Return(second)
|
||||
|
||||
// test for buffers bigger than freeListMaxScriptSize
|
||||
expectedCapacityBig := freeListMaxScriptSize + 1
|
||||
expectedLengthBig := expectedCapacityBig
|
||||
big := list.Borrow(uint64(expectedCapacityBig))
|
||||
|
||||
if cap(big) != expectedCapacityBig {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected capacity for second %d, but got %d",
|
||||
expectedCapacityBig, cap(big))
|
||||
}
|
||||
if len(big) != expectedLengthBig {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected length for second %d, but got %d",
|
||||
expectedLengthBig, len(big))
|
||||
}
|
||||
|
||||
list.Return(big)
|
||||
|
||||
// test there's no crash when channel is full because borrowed too much
|
||||
buffers := make([][]byte, freeListMaxItems+1)
|
||||
for i := 0; i < freeListMaxItems+1; i++ {
|
||||
buffers[i] = list.Borrow(1)
|
||||
}
|
||||
for i := 0; i < freeListMaxItems+1; i++ {
|
||||
list.Return(buffers[i])
|
||||
}
|
||||
}
|
||||
|
||||
func underlyingArrayAddress(buf []byte) uint64 {
|
||||
return uint64((*reflect.SliceHeader)(unsafe.Pointer(&buf)).Data)
|
||||
}
|
||||
|
||||
// multiTx is a MsgTx with an input and output and used in various tests.
|
||||
var multiTxIns = []*TxIn{
|
||||
{
|
||||
PreviousOutpoint: Outpoint{
|
||||
TxID: daghash.TxID{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
SignatureScript: []byte{
|
||||
0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62,
|
||||
},
|
||||
Sequence: math.MaxUint64,
|
||||
},
|
||||
}
|
||||
var multiTxOuts = []*TxOut{
|
||||
{
|
||||
Value: 0x12a05f200,
|
||||
ScriptPubKey: []byte{
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: 0x5f5e100,
|
||||
ScriptPubKey: []byte{
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
},
|
||||
},
|
||||
}
|
||||
var multiTx = NewNativeMsgTx(1, multiTxIns, multiTxOuts)
|
||||
|
||||
// multiTxEncoded is the appmessage encoded bytes for multiTx using protocol version
|
||||
// 60002 and is used in the various tests.
|
||||
var multiTxEncoded = []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x01, // Varint for number of input transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||
0x07, // Varint for length of signature script
|
||||
0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62, // Signature script
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence
|
||||
0x02, // Varint for number of output transactions
|
||||
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0x43, // Varint for length of scriptPubKey
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
0x00, 0xe1, 0xf5, 0x05, 0x00, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0x43, // Varint for length of scriptPubKey
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
}
|
||||
|
||||
// multiTxScriptPubKeyLocs is the location information for the public key scripts
|
||||
// located in multiTx.
|
||||
var multiTxScriptPubKeyLocs = []int{67, 143}
|
||||
|
||||
@@ -6,13 +6,14 @@ package appmessage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// MaxUserAgentLen is the maximum allowed length for the user agent field in a
|
||||
@@ -54,13 +55,13 @@ type MsgVersion struct {
|
||||
UserAgent string
|
||||
|
||||
// The selected tip hash of the generator of the version message.
|
||||
SelectedTipHash *externalapi.DomainHash
|
||||
SelectedTipHash *daghash.Hash
|
||||
|
||||
// Don't announce transactions to peer.
|
||||
DisableRelayTx bool
|
||||
|
||||
// The subnetwork of the generator of the version message. Should be nil in full nodes
|
||||
SubnetworkID *externalapi.DomainSubnetworkID
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
}
|
||||
|
||||
// HasService returns whether the specified service is supported by the peer
|
||||
@@ -85,7 +86,7 @@ func (msg *MsgVersion) Command() MessageCommand {
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgVersion(addr *NetAddress, id *id.ID, network string,
|
||||
selectedTipHash *externalapi.DomainHash, subnetworkID *externalapi.DomainSubnetworkID) *MsgVersion {
|
||||
selectedTipHash *daghash.Hash, subnetworkID *subnetworkid.SubnetworkID) *MsgVersion {
|
||||
|
||||
// Limit the timestamp to one millisecond precision since the protocol
|
||||
// doesn't support better.
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
)
|
||||
|
||||
// TestVersion tests the MsgVersion API.
|
||||
@@ -19,7 +18,7 @@ func TestVersion(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
// Create version message data.
|
||||
selectedTipHash := &externalapi.DomainHash{12, 34}
|
||||
selectedTipHash := &daghash.Hash{12, 34}
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 16111}
|
||||
me := NewNetAddress(tcpAddrMe, SFNodeNetwork)
|
||||
generatedID, err := id.GenerateID()
|
||||
@@ -45,7 +44,7 @@ func TestVersion(t *testing.T) {
|
||||
t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v",
|
||||
msg.UserAgent, DefaultUserAgent)
|
||||
}
|
||||
if *msg.SelectedTipHash != *selectedTipHash {
|
||||
if !msg.SelectedTipHash.IsEqual(selectedTipHash) {
|
||||
t.Errorf("NewMsgVersion: wrong selected tip hash - got %s, want %s",
|
||||
msg.SelectedTipHash, selectedTipHash)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ type GetBlockRequestMessage struct {
|
||||
baseMessage
|
||||
Hash string
|
||||
SubnetworkID string
|
||||
IncludeBlockHex bool
|
||||
IncludeBlockVerboseData bool
|
||||
IncludeTransactionVerboseData bool
|
||||
}
|
||||
|
||||
@@ -15,10 +17,13 @@ func (msg *GetBlockRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetBlockRequestMessage returns a instance of the message
|
||||
func NewGetBlockRequestMessage(hash string, subnetworkID string, includeTransactionVerboseData bool) *GetBlockRequestMessage {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -27,6 +32,7 @@ func NewGetBlockRequestMessage(hash string, subnetworkID string, includeTransact
|
||||
// its respective RPC message
|
||||
type GetBlockResponseMessage struct {
|
||||
baseMessage
|
||||
BlockHex string
|
||||
BlockVerboseData *BlockVerboseData
|
||||
|
||||
Error *RPCError
|
||||
@@ -45,6 +51,10 @@ func NewGetBlockResponseMessage() *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
|
||||
@@ -58,14 +68,16 @@ type BlockVerboseData struct {
|
||||
Difficulty float64
|
||||
ParentHashes []string
|
||||
SelectedParentHash string
|
||||
BlueScore uint64
|
||||
ChildHashes []string
|
||||
AcceptedBlockHashes []string
|
||||
}
|
||||
|
||||
// TransactionVerboseData holds verbose data about a transaction
|
||||
type TransactionVerboseData struct {
|
||||
Hex string
|
||||
TxID string
|
||||
Hash string
|
||||
Size uint64
|
||||
Size int32
|
||||
Version int32
|
||||
LockTime uint64
|
||||
SubnetworkID string
|
||||
@@ -75,6 +87,8 @@ type TransactionVerboseData struct {
|
||||
TransactionVerboseInputs []*TransactionVerboseInput
|
||||
TransactionVerboseOutputs []*TransactionVerboseOutput
|
||||
BlockHash string
|
||||
AcceptedBy string
|
||||
IsInMempool bool
|
||||
Time uint64
|
||||
BlockTime uint64
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package appmessage
|
||||
type GetBlockTemplateRequestMessage struct {
|
||||
baseMessage
|
||||
PayAddress string
|
||||
LongPollID string
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
@@ -13,9 +14,10 @@ func (msg *GetBlockTemplateRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetBlockTemplateRequestMessage returns a instance of the message
|
||||
func NewGetBlockTemplateRequestMessage(payAddress string) *GetBlockTemplateRequestMessage {
|
||||
func NewGetBlockTemplateRequestMessage(payAddress string, longPollID string) *GetBlockTemplateRequestMessage {
|
||||
return &GetBlockTemplateRequestMessage{
|
||||
PayAddress: payAddress,
|
||||
LongPollID: longPollID,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +25,23 @@ func NewGetBlockTemplateRequestMessage(payAddress string) *GetBlockTemplateReque
|
||||
// its respective RPC message
|
||||
type GetBlockTemplateResponseMessage struct {
|
||||
baseMessage
|
||||
MsgBlock *MsgBlock
|
||||
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
|
||||
}
|
||||
@@ -34,6 +52,27 @@ func (msg *GetBlockTemplateResponseMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetBlockTemplateResponseMessage returns a instance of the message
|
||||
func NewGetBlockTemplateResponseMessage(msgBlock *MsgBlock) *GetBlockTemplateResponseMessage {
|
||||
return &GetBlockTemplateResponseMessage{MsgBlock: msgBlock}
|
||||
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{}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ package appmessage
|
||||
// its respective RPC message
|
||||
type SubmitBlockRequestMessage struct {
|
||||
baseMessage
|
||||
Block *MsgBlock
|
||||
BlockHex string
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
@@ -13,9 +13,9 @@ func (msg *SubmitBlockRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewSubmitBlockRequestMessage returns a instance of the message
|
||||
func NewSubmitBlockRequestMessage(block *MsgBlock) *SubmitBlockRequestMessage {
|
||||
func NewSubmitBlockRequestMessage(blockHex string) *SubmitBlockRequestMessage {
|
||||
return &SubmitBlockRequestMessage{
|
||||
Block: block,
|
||||
BlockHex: blockHex,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ package appmessage
|
||||
// its respective RPC message
|
||||
type SubmitTransactionRequestMessage struct {
|
||||
baseMessage
|
||||
Transaction *MsgTx
|
||||
TransactionHex string
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
@@ -13,9 +13,9 @@ func (msg *SubmitTransactionRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewSubmitTransactionRequestMessage returns a instance of the message
|
||||
func NewSubmitTransactionRequestMessage(transaction *MsgTx) *SubmitTransactionRequestMessage {
|
||||
func NewSubmitTransactionRequestMessage(transactionHex string) *SubmitTransactionRequestMessage {
|
||||
return &SubmitTransactionRequestMessage{
|
||||
Transaction: transaction,
|
||||
TransactionHex: transactionHex,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,6 @@ import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
infrastructuredatabase "github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
@@ -15,7 +11,13 @@ import (
|
||||
"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"
|
||||
@@ -75,14 +77,19 @@ func (a *ComponentManager) Stop() {
|
||||
|
||||
// NewComponentManager returns a new ComponentManager instance.
|
||||
// Use Start() to begin all services within this ComponentManager
|
||||
func NewComponentManager(cfg *config.Config, db infrastructuredatabase.Database, interrupt chan<- struct{}) (
|
||||
*ComponentManager, error) {
|
||||
func NewComponentManager(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt chan<- struct{}) (*ComponentManager, error) {
|
||||
indexManager, acceptanceIndex := setupIndexes(cfg)
|
||||
|
||||
domain, err := domain.New(cfg.ActiveNetParams, db)
|
||||
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
|
||||
@@ -97,11 +104,13 @@ func NewComponentManager(cfg *config.Config, db infrastructuredatabase.Database,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocolManager, err := protocol.NewManager(cfg, domain, netAdapter, addressManager, connectionManager)
|
||||
|
||||
protocolManager, err := protocol.NewManager(cfg, dag, netAdapter, addressManager, txMempool, connectionManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpcManager := setupRPC(cfg, domain, netAdapter, protocolManager, connectionManager, addressManager, interrupt)
|
||||
|
||||
rpcManager := setupRPC(cfg, txMempool, dag, sigCache, netAdapter, protocolManager, connectionManager, addressManager, acceptanceIndex, interrupt)
|
||||
|
||||
return &ComponentManager{
|
||||
cfg: cfg,
|
||||
@@ -116,21 +125,60 @@ func NewComponentManager(cfg *config.Config, db infrastructuredatabase.Database,
|
||||
|
||||
func setupRPC(
|
||||
cfg *config.Config,
|
||||
domain domain.Domain,
|
||||
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 {
|
||||
|
||||
rpcManager := rpc.NewManager(
|
||||
cfg, domain, netAdapter, protocolManager, connectionManager, addressManager, shutDownChan)
|
||||
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,
|
||||
@@ -150,6 +198,56 @@ func (a *ComponentManager) maybeSeedFromDNS() {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
package blocklogger
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,17 +22,17 @@ var (
|
||||
// LogBlock logs a new block blue score as an information message
|
||||
// to show progress to the user. In order to prevent spam, it limits logging to
|
||||
// one message every 10 seconds with duration and totals included.
|
||||
func LogBlock(block *externalapi.DomainBlock) {
|
||||
func LogBlock(block *util.Block) error {
|
||||
mtx.Lock()
|
||||
defer mtx.Unlock()
|
||||
|
||||
receivedLogBlocks++
|
||||
receivedLogTx += int64(len(block.Transactions))
|
||||
receivedLogTx += int64(len(block.MsgBlock().Transactions))
|
||||
|
||||
now := mstime.Now()
|
||||
duration := now.Sub(lastBlockLogTime)
|
||||
if duration < time.Second*10 {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Truncate the duration to 10s of milliseconds.
|
||||
@@ -48,11 +48,17 @@ func LogBlock(block *externalapi.DomainBlock) {
|
||||
txStr = "transaction"
|
||||
}
|
||||
|
||||
log.Infof("Processed %d %s in the last %s (%d %s, %s)",
|
||||
blueScore, err := block.BlueScore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Processed %d %s in the last %s (%d %s, blue score %d, %s)",
|
||||
receivedLogBlocks, blockStr, tDuration, receivedLogTx,
|
||||
txStr, mstime.UnixMilliseconds(block.Header.TimeInMilliseconds))
|
||||
txStr, blueScore, block.MsgBlock().Header.Timestamp)
|
||||
|
||||
receivedLogBlocks = 0
|
||||
receivedLogTx = 0
|
||||
lastBlockLogTime = now
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,47 +1,34 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/blocklogger"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/pkg/errors"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// OnNewBlock updates the mempool after a new block arrival, and
|
||||
// relays newly unorphaned transactions and possibly rebroadcast
|
||||
// manually added transactions when not in IBD.
|
||||
func (f *FlowContext) OnNewBlock(block *externalapi.DomainBlock) error {
|
||||
unorphanedBlocks, err := f.UnorphanBlocks(block)
|
||||
func (f *FlowContext) OnNewBlock(block *util.Block) error {
|
||||
transactionsAcceptedToMempool, err := f.txPool.HandleNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newBlocks := append([]*externalapi.DomainBlock{block}, unorphanedBlocks...)
|
||||
for _, newBlock := range newBlocks {
|
||||
blocklogger.LogBlock(block)
|
||||
|
||||
_ = f.Domain().MiningManager().HandleNewBlockTransactions(newBlock.Transactions)
|
||||
|
||||
if f.onBlockAddedToDAGHandler != nil {
|
||||
err := f.onBlockAddedToDAGHandler(newBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.onBlockAddedToDAGHandler != nil {
|
||||
err := f.onBlockAddedToDAGHandler(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return f.broadcastTransactionsAfterBlockAdded(block, transactionsAcceptedToMempool)
|
||||
}
|
||||
|
||||
func (f *FlowContext) broadcastTransactionsAfterBlockAdded(
|
||||
block *externalapi.DomainBlock, transactionsAcceptedToMempool []*externalapi.DomainTransaction) error {
|
||||
|
||||
func (f *FlowContext) broadcastTransactionsAfterBlockAdded(block *util.Block, transactionsAcceptedToMempool []*util.Tx) error {
|
||||
f.updateTransactionsToRebroadcast(block)
|
||||
|
||||
// Don't relay transactions when in IBD.
|
||||
@@ -49,14 +36,14 @@ func (f *FlowContext) broadcastTransactionsAfterBlockAdded(
|
||||
return nil
|
||||
}
|
||||
|
||||
var txIDsToRebroadcast []*externalapi.DomainTransactionID
|
||||
var txIDsToRebroadcast []*daghash.TxID
|
||||
if f.shouldRebroadcastTransactions() {
|
||||
txIDsToRebroadcast = f.txIDsToRebroadcast()
|
||||
}
|
||||
|
||||
txIDsToBroadcast := make([]*externalapi.DomainTransactionID, len(transactionsAcceptedToMempool)+len(txIDsToRebroadcast))
|
||||
txIDsToBroadcast := make([]*daghash.TxID, len(transactionsAcceptedToMempool)+len(txIDsToRebroadcast))
|
||||
for i, tx := range transactionsAcceptedToMempool {
|
||||
txIDsToBroadcast[i] = consensusserialization.TransactionID(tx)
|
||||
txIDsToBroadcast[i] = tx.ID()
|
||||
}
|
||||
offset := len(transactionsAcceptedToMempool)
|
||||
for i, txID := range txIDsToRebroadcast {
|
||||
@@ -80,18 +67,14 @@ func (f *FlowContext) SharedRequestedBlocks() *blockrelay.SharedRequestedBlocks
|
||||
}
|
||||
|
||||
// AddBlock adds the given block to the DAG and propagates it.
|
||||
func (f *FlowContext) AddBlock(block *externalapi.DomainBlock) error {
|
||||
err := f.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
func (f *FlowContext) AddBlock(block *util.Block, flags blockdag.BehaviorFlags) error {
|
||||
_, _, err := f.DAG().ProcessBlock(block, flags)
|
||||
if err != nil {
|
||||
if errors.As(err, &ruleerrors.RuleError{}) {
|
||||
log.Infof("Validation failed for block %s: %s", consensusserialization.BlockHash(block), err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
err = f.OnNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Broadcast(appmessage.NewMsgInvBlock(consensusserialization.BlockHash(block)))
|
||||
return f.Broadcast(appmessage.NewMsgInvBlock(block.Hash()))
|
||||
}
|
||||
|
||||
8
app/protocol/flowcontext/consensus.go
Normal file
8
app/protocol/flowcontext/consensus.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package flowcontext
|
||||
|
||||
import "github.com/kaspanet/kaspad/domain/blockdag"
|
||||
|
||||
// DAG returns the DAG associated to the flow context.
|
||||
func (f *FlowContext) DAG() *blockdag.BlockDAG {
|
||||
return f.dag
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
)
|
||||
|
||||
// Domain returns the Domain object associated to the flow context.
|
||||
func (f *FlowContext) Domain() domain.Domain {
|
||||
return f.domain
|
||||
}
|
||||
@@ -4,23 +4,23 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/relaytransactions"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// OnBlockAddedToDAGHandler is a handler function that's triggered
|
||||
// when a block is added to the DAG
|
||||
type OnBlockAddedToDAGHandler func(block *externalapi.DomainBlock) error
|
||||
type OnBlockAddedToDAGHandler func(block *util.Block) error
|
||||
|
||||
// OnTransactionAddedToMempoolHandler is a handler function that's triggered
|
||||
// when a transaction is added to the mempool
|
||||
@@ -31,7 +31,8 @@ type OnTransactionAddedToMempoolHandler func()
|
||||
type FlowContext struct {
|
||||
cfg *config.Config
|
||||
netAdapter *netadapter.NetAdapter
|
||||
domain domain.Domain
|
||||
txPool *mempool.TxPool
|
||||
dag *blockdag.BlockDAG
|
||||
addressManager *addressmanager.AddressManager
|
||||
connectionManager *connmanager.ConnectionManager
|
||||
|
||||
@@ -39,7 +40,7 @@ type FlowContext struct {
|
||||
onTransactionAddedToMempoolHandler OnTransactionAddedToMempoolHandler
|
||||
|
||||
transactionsToRebroadcastLock sync.Mutex
|
||||
transactionsToRebroadcast map[externalapi.DomainTransactionID]*externalapi.DomainTransaction
|
||||
transactionsToRebroadcast map[daghash.TxID]*util.Tx
|
||||
lastRebroadcastTime time.Time
|
||||
sharedRequestedTransactions *relaytransactions.SharedRequestedTransactions
|
||||
|
||||
@@ -51,26 +52,24 @@ type FlowContext struct {
|
||||
|
||||
peers map[id.ID]*peerpkg.Peer
|
||||
peersMutex sync.RWMutex
|
||||
|
||||
orphans map[externalapi.DomainHash]*externalapi.DomainBlock
|
||||
orphansMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// New returns a new instance of FlowContext.
|
||||
func New(cfg *config.Config, domain domain.Domain, addressManager *addressmanager.AddressManager,
|
||||
netAdapter *netadapter.NetAdapter, connectionManager *connmanager.ConnectionManager) *FlowContext {
|
||||
func New(cfg *config.Config, dag *blockdag.BlockDAG, addressManager *addressmanager.AddressManager,
|
||||
txPool *mempool.TxPool, netAdapter *netadapter.NetAdapter,
|
||||
connectionManager *connmanager.ConnectionManager) *FlowContext {
|
||||
|
||||
return &FlowContext{
|
||||
cfg: cfg,
|
||||
netAdapter: netAdapter,
|
||||
domain: domain,
|
||||
dag: dag,
|
||||
addressManager: addressManager,
|
||||
connectionManager: connectionManager,
|
||||
txPool: txPool,
|
||||
sharedRequestedTransactions: relaytransactions.NewSharedRequestedTransactions(),
|
||||
sharedRequestedBlocks: blockrelay.NewSharedRequestedBlocks(),
|
||||
peers: make(map[id.ID]*peerpkg.Peer),
|
||||
transactionsToRebroadcast: make(map[externalapi.DomainTransactionID]*externalapi.DomainTransaction),
|
||||
orphans: make(map[externalapi.DomainHash]*externalapi.DomainBlock),
|
||||
transactionsToRebroadcast: make(map[daghash.TxID]*util.Tx),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +1,32 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
)
|
||||
|
||||
// StartIBDIfRequired selects a peer and starts IBD against it
|
||||
// if required
|
||||
func (f *FlowContext) StartIBDIfRequired() error {
|
||||
func (f *FlowContext) StartIBDIfRequired() {
|
||||
f.startIBDMutex.Lock()
|
||||
defer f.startIBDMutex.Unlock()
|
||||
|
||||
if f.IsInIBD() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
syncInfo, err := f.domain.Consensus().GetSyncInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if syncInfo.State == externalapi.SyncStateRelay {
|
||||
return nil
|
||||
}
|
||||
|
||||
peer, err := f.selectPeerForIBD(syncInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
peer := f.selectPeerForIBD(f.dag)
|
||||
if peer == nil {
|
||||
spawn("StartIBDIfRequired-requestSelectedTipsIfRequired", f.requestSelectedTipsIfRequired)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&f.isInIBD, 1)
|
||||
f.ibdPeer = peer
|
||||
spawn("StartIBDIfRequired-peer.StartIBD", peer.StartIBD)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsInIBD is true if IBD is currently running
|
||||
@@ -52,54 +36,29 @@ func (f *FlowContext) IsInIBD() bool {
|
||||
|
||||
// selectPeerForIBD returns the first peer whose selected tip
|
||||
// hash is not in our DAG
|
||||
func (f *FlowContext) selectPeerForIBD(syncInfo *externalapi.SyncInfo) (*peerpkg.Peer, error) {
|
||||
func (f *FlowContext) selectPeerForIBD(dag *blockdag.BlockDAG) *peerpkg.Peer {
|
||||
f.peersMutex.RLock()
|
||||
defer f.peersMutex.RUnlock()
|
||||
|
||||
for _, peer := range f.peers {
|
||||
peerSelectedTipHash := peer.SelectedTipHash()
|
||||
|
||||
if f.IsOrphan(peerSelectedTipHash) {
|
||||
continue
|
||||
}
|
||||
|
||||
blockInfo, err := f.domain.Consensus().GetBlockInfo(peerSelectedTipHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if syncInfo.State == externalapi.SyncStateHeadersFirst {
|
||||
if !blockInfo.Exists {
|
||||
return peer, nil
|
||||
}
|
||||
} else {
|
||||
if blockInfo.Exists && blockInfo.BlockStatus == externalapi.StatusHeaderOnly &&
|
||||
blockInfo.IsBlockInHeaderPruningPointFuture {
|
||||
return peer, nil
|
||||
}
|
||||
if !dag.IsInDAG(peerSelectedTipHash) {
|
||||
return peer
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlowContext) requestSelectedTipsIfRequired() {
|
||||
dagTimeCurrent, err := f.shouldRequestSelectedTips()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if dagTimeCurrent {
|
||||
if f.isDAGTimeCurrent() {
|
||||
return
|
||||
}
|
||||
f.requestSelectedTips()
|
||||
}
|
||||
|
||||
func (f *FlowContext) shouldRequestSelectedTips() (bool, error) {
|
||||
func (f *FlowContext) isDAGTimeCurrent() bool {
|
||||
const minDurationToRequestSelectedTips = time.Minute
|
||||
virtualSelectedParent, err := f.domain.Consensus().GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
virtualSelectedParentTime := mstime.UnixMilliseconds(virtualSelectedParent.Header.TimeInMilliseconds)
|
||||
return mstime.Now().Sub(virtualSelectedParentTime) > minDurationToRequestSelectedTips, nil
|
||||
return f.dag.Now().Sub(f.dag.SelectedTipHeader().Timestamp) > minDurationToRequestSelectedTips
|
||||
}
|
||||
|
||||
func (f *FlowContext) requestSelectedTips() {
|
||||
@@ -112,12 +71,12 @@ func (f *FlowContext) requestSelectedTips() {
|
||||
}
|
||||
|
||||
// FinishIBD finishes the current IBD flow and starts a new one if required.
|
||||
func (f *FlowContext) FinishIBD() error {
|
||||
func (f *FlowContext) FinishIBD() {
|
||||
f.ibdPeer = nil
|
||||
|
||||
atomic.StoreUint32(&f.isInIBD, 0)
|
||||
|
||||
return f.StartIBDIfRequired()
|
||||
f.StartIBDIfRequired()
|
||||
}
|
||||
|
||||
// IBDPeer returns the currently active IBD peer.
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// AddOrphan adds the block to the orphan set
|
||||
func (f *FlowContext) AddOrphan(orphanBlock *externalapi.DomainBlock) {
|
||||
f.orphansMutex.Lock()
|
||||
defer f.orphansMutex.Unlock()
|
||||
|
||||
orphanHash := consensusserialization.BlockHash(orphanBlock)
|
||||
f.orphans[*orphanHash] = orphanBlock
|
||||
|
||||
log.Infof("Received a block with missing parents, adding to orphan pool: %s", orphanHash)
|
||||
}
|
||||
|
||||
// IsOrphan returns whether the given blockHash belongs to an orphan block
|
||||
func (f *FlowContext) IsOrphan(blockHash *externalapi.DomainHash) bool {
|
||||
f.orphansMutex.RLock()
|
||||
defer f.orphansMutex.RUnlock()
|
||||
|
||||
_, ok := f.orphans[*blockHash]
|
||||
return ok
|
||||
}
|
||||
|
||||
// UnorphanBlocks removes the block from the orphan set, and remove all of the blocks that are not orphans anymore.
|
||||
func (f *FlowContext) UnorphanBlocks(rootBlock *externalapi.DomainBlock) ([]*externalapi.DomainBlock, error) {
|
||||
f.orphansMutex.Lock()
|
||||
defer f.orphansMutex.Unlock()
|
||||
|
||||
// Find all the children of rootBlock among the orphans
|
||||
// and add them to the process queue
|
||||
rootBlockHash := consensusserialization.BlockHash(rootBlock)
|
||||
processQueue := f.addChildOrphansToProcessQueue(rootBlockHash, []externalapi.DomainHash{})
|
||||
|
||||
var unorphanedBlocks []*externalapi.DomainBlock
|
||||
for len(processQueue) > 0 {
|
||||
var orphanHash externalapi.DomainHash
|
||||
orphanHash, processQueue = processQueue[0], processQueue[1:]
|
||||
orphanBlock := f.orphans[orphanHash]
|
||||
|
||||
log.Tracef("Considering to unorphan block %s with parents %s",
|
||||
orphanHash, orphanBlock.Header.ParentHashes)
|
||||
|
||||
canBeUnorphaned := true
|
||||
for _, orphanBlockParentHash := range orphanBlock.Header.ParentHashes {
|
||||
orphanBlockParentInfo, err := f.domain.Consensus().GetBlockInfo(orphanBlockParentHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !orphanBlockParentInfo.Exists {
|
||||
log.Tracef("Cannot unorphan block %s. It's missing at "+
|
||||
"least the following parent: %s", orphanHash, orphanBlockParentHash)
|
||||
|
||||
canBeUnorphaned = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if canBeUnorphaned {
|
||||
err := f.unorphanBlock(orphanHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unorphanedBlocks = append(unorphanedBlocks, orphanBlock)
|
||||
processQueue = f.addChildOrphansToProcessQueue(&orphanHash, processQueue)
|
||||
}
|
||||
}
|
||||
|
||||
return unorphanedBlocks, nil
|
||||
}
|
||||
|
||||
// addChildOrphansToProcessQueue finds all child orphans of `blockHash`
|
||||
// and adds them to the given `processQueue` if they don't already exist
|
||||
// inside of it
|
||||
// Note that this method does not modify the given `processQueue`
|
||||
func (f *FlowContext) addChildOrphansToProcessQueue(blockHash *externalapi.DomainHash,
|
||||
processQueue []externalapi.DomainHash) []externalapi.DomainHash {
|
||||
|
||||
blockChildren := f.findChildOrphansOfBlock(blockHash)
|
||||
for _, blockChild := range blockChildren {
|
||||
exists := false
|
||||
for _, queueOrphan := range processQueue {
|
||||
if queueOrphan == blockChild {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
processQueue = append(processQueue, blockChild)
|
||||
}
|
||||
}
|
||||
return processQueue
|
||||
}
|
||||
|
||||
func (f *FlowContext) findChildOrphansOfBlock(blockHash *externalapi.DomainHash) []externalapi.DomainHash {
|
||||
var childOrphans []externalapi.DomainHash
|
||||
for orphanHash, orphanBlock := range f.orphans {
|
||||
for _, orphanBlockParentHash := range orphanBlock.Header.ParentHashes {
|
||||
if *orphanBlockParentHash == *blockHash {
|
||||
childOrphans = append(childOrphans, orphanHash)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return childOrphans
|
||||
}
|
||||
|
||||
func (f *FlowContext) unorphanBlock(orphanHash externalapi.DomainHash) error {
|
||||
orphanBlock, ok := f.orphans[orphanHash]
|
||||
if !ok {
|
||||
return errors.Errorf("attempted to unorphan a non-orphan block %s", orphanHash)
|
||||
}
|
||||
delete(f.orphans, orphanHash)
|
||||
|
||||
err := f.domain.Consensus().ValidateAndInsertBlock(orphanBlock)
|
||||
if err != nil {
|
||||
if errors.As(err, &ruleerrors.RuleError{}) {
|
||||
log.Infof("Validation failed for orphan block %s: %s", orphanHash, err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Unorphaned block %s", orphanHash)
|
||||
return nil
|
||||
}
|
||||
@@ -1,38 +1,42 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/relaytransactions"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AddTransaction adds transaction to the mempool and propagates it.
|
||||
func (f *FlowContext) AddTransaction(tx *externalapi.DomainTransaction) error {
|
||||
func (f *FlowContext) AddTransaction(tx *util.Tx) error {
|
||||
f.transactionsToRebroadcastLock.Lock()
|
||||
defer f.transactionsToRebroadcastLock.Unlock()
|
||||
|
||||
err := f.Domain().MiningManager().ValidateAndInsertTransaction(tx, false)
|
||||
transactionsAcceptedToMempool, err := f.txPool.ProcessTransaction(tx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transactionID := consensusserialization.TransactionID(tx)
|
||||
f.transactionsToRebroadcast[*transactionID] = tx
|
||||
inv := appmessage.NewMsgInvTransaction([]*externalapi.DomainTransactionID{transactionID})
|
||||
if len(transactionsAcceptedToMempool) > 1 {
|
||||
return errors.New("got more than one accepted transactions when no orphans were allowed")
|
||||
}
|
||||
|
||||
f.transactionsToRebroadcast[*tx.ID()] = tx
|
||||
inv := appmessage.NewMsgInvTransaction([]*daghash.TxID{tx.ID()})
|
||||
return f.Broadcast(inv)
|
||||
}
|
||||
|
||||
func (f *FlowContext) updateTransactionsToRebroadcast(block *externalapi.DomainBlock) {
|
||||
func (f *FlowContext) updateTransactionsToRebroadcast(block *util.Block) {
|
||||
f.transactionsToRebroadcastLock.Lock()
|
||||
defer f.transactionsToRebroadcastLock.Unlock()
|
||||
// Note: if the block is red, its transactions won't be rebroadcasted
|
||||
// anymore, although they are not included in the UTXO set.
|
||||
// This is probably ok, since red blocks are quite rare.
|
||||
for _, tx := range block.Transactions {
|
||||
delete(f.transactionsToRebroadcast, *consensusserialization.TransactionID(tx))
|
||||
for _, tx := range block.Transactions() {
|
||||
delete(f.transactionsToRebroadcast, *tx.ID())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,14 +45,14 @@ func (f *FlowContext) shouldRebroadcastTransactions() bool {
|
||||
return time.Since(f.lastRebroadcastTime) > rebroadcastInterval
|
||||
}
|
||||
|
||||
func (f *FlowContext) txIDsToRebroadcast() []*externalapi.DomainTransactionID {
|
||||
func (f *FlowContext) txIDsToRebroadcast() []*daghash.TxID {
|
||||
f.transactionsToRebroadcastLock.Lock()
|
||||
defer f.transactionsToRebroadcastLock.Unlock()
|
||||
|
||||
txIDs := make([]*externalapi.DomainTransactionID, len(f.transactionsToRebroadcast))
|
||||
txIDs := make([]*daghash.TxID, len(f.transactionsToRebroadcast))
|
||||
i := 0
|
||||
for _, tx := range f.transactionsToRebroadcast {
|
||||
txIDs[i] = consensusserialization.TransactionID(tx)
|
||||
txIDs[i] = tx.ID()
|
||||
i++
|
||||
}
|
||||
return txIDs
|
||||
@@ -60,6 +64,11 @@ func (f *FlowContext) SharedRequestedTransactions() *relaytransactions.SharedReq
|
||||
return f.sharedRequestedTransactions
|
||||
}
|
||||
|
||||
// TxPool returns the transaction pool associated to the manager.
|
||||
func (f *FlowContext) TxPool() *mempool.TxPool {
|
||||
return f.txPool
|
||||
}
|
||||
|
||||
// OnTransactionAddedToMempool notifies the handler function that a transaction
|
||||
// has been added to the mempool
|
||||
func (f *FlowContext) OnTransactionAddedToMempool() {
|
||||
|
||||
@@ -33,10 +33,20 @@ func ReceiveAddresses(context ReceiveAddressesContext, incomingRoute *router.Rou
|
||||
}
|
||||
|
||||
msgAddresses := message.(*appmessage.MsgAddresses)
|
||||
if len(msgAddresses.AddressList) > addressmanager.GetAddressesMax {
|
||||
if len(msgAddresses.AddrList) > addressmanager.GetAddressesMax {
|
||||
return protocolerrors.Errorf(true, "address count exceeded %d", addressmanager.GetAddressesMax)
|
||||
}
|
||||
|
||||
context.AddressManager().AddAddresses(msgAddresses.AddressList...)
|
||||
if msgAddresses.IncludeAllSubnetworks {
|
||||
return protocolerrors.Errorf(true, "got unexpected "+
|
||||
"IncludeAllSubnetworks=true in [%s] command", msgAddresses.Command())
|
||||
}
|
||||
if !msgAddresses.SubnetworkID.IsEqual(context.Config().SubnetworkID) && msgAddresses.SubnetworkID != nil {
|
||||
return protocolerrors.Errorf(false, "only full nodes and %s subnetwork IDs "+
|
||||
"are allowed in [%s] command, but got subnetwork ID %s",
|
||||
context.Config().SubnetworkID, msgAddresses.Command(), msgAddresses.SubnetworkID)
|
||||
}
|
||||
|
||||
context.AddressManager().AddAddresses(msgAddresses.AddrList...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package addressexchange
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"math/rand"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
@@ -16,25 +15,20 @@ type SendAddressesContext interface {
|
||||
|
||||
// SendAddresses sends addresses to a peer that requests it.
|
||||
func SendAddresses(context SendAddressesContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
||||
for {
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, ok := message.(*appmessage.MsgRequestAddresses)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "unexpected message. "+
|
||||
"Expected: %s, got: %s", appmessage.CmdRequestAddresses, message.Command())
|
||||
}
|
||||
addresses := context.AddressManager().Addresses()
|
||||
msgAddresses := appmessage.NewMsgAddresses(shuffleAddresses(addresses))
|
||||
|
||||
err = outgoingRoute.Enqueue(msgAddresses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgGetAddresses := message.(*appmessage.MsgRequestAddresses)
|
||||
addresses := context.AddressManager().Addresses()
|
||||
msgAddresses := appmessage.NewMsgAddresses(msgGetAddresses.IncludeAllSubnetworks, msgGetAddresses.SubnetworkID)
|
||||
err = msgAddresses.AddAddresses(shuffleAddresses(addresses)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return outgoingRoute.Enqueue(msgAddresses)
|
||||
}
|
||||
|
||||
// shuffleAddresses randomizes the given addresses sent if there are more than the maximum allowed in one message.
|
||||
|
||||
@@ -4,14 +4,14 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// RelayBlockRequestsContext is the interface for the context needed for the HandleRelayBlockRequests flow.
|
||||
type RelayBlockRequestsContext interface {
|
||||
Domain() domain.Domain
|
||||
DAG() *blockdag.BlockDAG
|
||||
}
|
||||
|
||||
// HandleRelayBlockRequests listens to appmessage.MsgRequestRelayBlocks messages and sends
|
||||
@@ -27,21 +27,26 @@ func HandleRelayBlockRequests(context RelayBlockRequestsContext, incomingRoute *
|
||||
getRelayBlocksMessage := message.(*appmessage.MsgRequestRelayBlocks)
|
||||
for _, hash := range getRelayBlocksMessage.Hashes {
|
||||
// Fetch the block from the database.
|
||||
blockInfo, err := context.Domain().Consensus().GetBlockInfo(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !blockInfo.Exists {
|
||||
block, err := context.DAG().BlockByHash(hash)
|
||||
if blockdag.IsNotInDAGErr(err) {
|
||||
return protocolerrors.Errorf(true, "block %s not found", hash)
|
||||
}
|
||||
block, err := context.Domain().Consensus().GetBlock(hash)
|
||||
if err != nil {
|
||||
} else if err != nil {
|
||||
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
|
||||
}
|
||||
msgBlock := block.MsgBlock()
|
||||
|
||||
// TODO (Partial nodes): Convert block to partial block if needed
|
||||
// If we are a full node and the peer is a partial node, we must convert
|
||||
// the block to a partial block.
|
||||
nodeSubnetworkID := context.DAG().SubnetworkID()
|
||||
peerSubnetworkID := peer.SubnetworkID()
|
||||
|
||||
err = outgoingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(block))
|
||||
isNodeFull := nodeSubnetworkID == nil
|
||||
isPeerFull := peerSubnetworkID == nil
|
||||
if isNodeFull && !isPeerFull {
|
||||
msgBlock.ConvertToPartial(peerSubnetworkID)
|
||||
}
|
||||
|
||||
err = outgoingRoute.Enqueue(msgBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,31 +2,28 @@ package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/blocklogger"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/blocks"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
mathUtil "github.com/kaspanet/kaspad/util/math"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// RelayInvsContext is the interface for the context needed for the HandleRelayInvs flow.
|
||||
type RelayInvsContext interface {
|
||||
Domain() domain.Domain
|
||||
NetAdapter() *netadapter.NetAdapter
|
||||
OnNewBlock(block *externalapi.DomainBlock) error
|
||||
DAG() *blockdag.BlockDAG
|
||||
OnNewBlock(block *util.Block) error
|
||||
SharedRequestedBlocks() *SharedRequestedBlocks
|
||||
StartIBDIfRequired() error
|
||||
StartIBDIfRequired()
|
||||
IsInIBD() bool
|
||||
Broadcast(message appmessage.Message) error
|
||||
AddOrphan(orphanBlock *externalapi.DomainBlock)
|
||||
IsOrphan(blockHash *externalapi.DomainHash) bool
|
||||
}
|
||||
|
||||
type handleRelayInvsFlow struct {
|
||||
@@ -60,26 +57,15 @@ func (flow *handleRelayInvsFlow) start() error {
|
||||
|
||||
log.Debugf("Got relay inv for block %s", inv.Hash)
|
||||
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(inv.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if blockInfo.Exists {
|
||||
if blockInfo.BlockStatus == externalapi.StatusInvalid {
|
||||
if flow.DAG().IsKnownBlock(inv.Hash) {
|
||||
if flow.DAG().IsKnownInvalid(inv.Hash) {
|
||||
return protocolerrors.Errorf(true, "sent inv of an invalid block %s",
|
||||
inv.Hash)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if flow.IsOrphan(inv.Hash) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = flow.StartIBDIfRequired()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flow.StartIBDIfRequired()
|
||||
if flow.IsInIBD() {
|
||||
// Block relay is disabled during IBD
|
||||
continue
|
||||
@@ -98,6 +84,7 @@ func (flow *handleRelayInvsFlow) start() error {
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) readInv() (*appmessage.MsgInvRelayBlock, error) {
|
||||
|
||||
if len(flow.invsQueue) > 0 {
|
||||
var inv *appmessage.MsgInvRelayBlock
|
||||
inv, flow.invsQueue = flow.invsQueue[0], flow.invsQueue[1:]
|
||||
@@ -118,11 +105,11 @@ func (flow *handleRelayInvsFlow) readInv() (*appmessage.MsgInvRelayBlock, error)
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) requestBlocks(requestQueue *hashesQueueSet) error {
|
||||
numHashesToRequest := mathUtil.MinInt(appmessage.MaxRequestRelayBlocksHashes, requestQueue.len())
|
||||
numHashesToRequest := mathUtil.MinInt(appmessage.MsgRequestRelayBlocksHashes, requestQueue.len())
|
||||
hashesToRequest := requestQueue.dequeue(numHashesToRequest)
|
||||
|
||||
pendingBlocks := map[externalapi.DomainHash]struct{}{}
|
||||
var filteredHashesToRequest []*externalapi.DomainHash
|
||||
pendingBlocks := map[daghash.Hash]struct{}{}
|
||||
var filteredHashesToRequest []*daghash.Hash
|
||||
for _, hash := range hashesToRequest {
|
||||
exists := flow.SharedRequestedBlocks().addIfNotExists(hash)
|
||||
if exists {
|
||||
@@ -130,11 +117,7 @@ func (flow *handleRelayInvsFlow) requestBlocks(requestQueue *hashesQueueSet) err
|
||||
}
|
||||
|
||||
// The block can become known from another peer in the process of orphan resolution
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if blockInfo.Exists {
|
||||
if flow.DAG().IsKnownBlock(hash) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -163,11 +146,11 @@ func (flow *handleRelayInvsFlow) requestBlocks(requestQueue *hashesQueueSet) err
|
||||
return err
|
||||
}
|
||||
|
||||
block := appmessage.MsgBlockToDomainBlock(msgBlock)
|
||||
blockHash := consensusserialization.BlockHash(block)
|
||||
block := util.NewBlock(msgBlock)
|
||||
blockHash := block.Hash()
|
||||
|
||||
if _, ok := pendingBlocks[*blockHash]; !ok {
|
||||
return protocolerrors.Errorf(true, "got unrequested block %s", blockHash)
|
||||
return protocolerrors.Errorf(true, "got unrequested block %s", block.Hash())
|
||||
}
|
||||
|
||||
err = flow.processAndRelayBlock(requestQueue, block)
|
||||
@@ -184,7 +167,9 @@ func (flow *handleRelayInvsFlow) requestBlocks(requestQueue *hashesQueueSet) err
|
||||
// readMsgBlock returns the next msgBlock in msgChan, and populates invsQueue with any inv messages that meanwhile arrive.
|
||||
//
|
||||
// Note: this function assumes msgChan can contain only appmessage.MsgInvRelayBlock and appmessage.MsgBlock messages.
|
||||
func (flow *handleRelayInvsFlow) readMsgBlock() (msgBlock *appmessage.MsgBlock, err error) {
|
||||
func (flow *handleRelayInvsFlow) readMsgBlock() (
|
||||
msgBlock *appmessage.MsgBlock, err error) {
|
||||
|
||||
for {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
@@ -202,64 +187,55 @@ func (flow *handleRelayInvsFlow) readMsgBlock() (msgBlock *appmessage.MsgBlock,
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) processAndRelayBlock(requestQueue *hashesQueueSet, block *externalapi.DomainBlock) error {
|
||||
blockHash := consensusserialization.BlockHash(block)
|
||||
err := flow.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
func (flow *handleRelayInvsFlow) processAndRelayBlock(requestQueue *hashesQueueSet, block *util.Block) error {
|
||||
blockHash := block.Hash()
|
||||
isOrphan, isDelayed, err := flow.DAG().ProcessBlock(block, blockdag.BFNone)
|
||||
if err != nil {
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
if !errors.As(err, &blockdag.RuleError{}) {
|
||||
return errors.Wrapf(err, "failed to process block %s", blockHash)
|
||||
}
|
||||
|
||||
missingParentsError := &ruleerrors.ErrMissingParents{}
|
||||
if errors.As(err, missingParentsError) {
|
||||
blueScore, err := blocks.ExtractBlueScore(block)
|
||||
if err != nil {
|
||||
return protocolerrors.Errorf(true, "received an orphan "+
|
||||
"block %s with malformed blue score", blockHash)
|
||||
}
|
||||
|
||||
const maxOrphanBlueScoreDiff = 10000
|
||||
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
selectedTipBlueScore, err := blocks.ExtractBlueScore(virtualSelectedParent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if blueScore > selectedTipBlueScore+maxOrphanBlueScoreDiff {
|
||||
log.Infof("Orphan block %s has blue score %d and the selected tip blue score is "+
|
||||
"%d. Ignoring orphans with a blue score difference from the selected tip greater than %d",
|
||||
blockHash, blueScore, selectedTipBlueScore, maxOrphanBlueScoreDiff)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add the orphan to the orphan pool
|
||||
flow.AddOrphan(block)
|
||||
|
||||
// Request the parents for the orphan block from the peer that sent it.
|
||||
for _, missingAncestor := range missingParentsError.MissingParentHashes {
|
||||
requestQueue.enqueueIfNotExists(missingAncestor)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
log.Infof("Rejected block %s from %s: %s", blockHash, flow.peer, err)
|
||||
|
||||
return protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash)
|
||||
}
|
||||
|
||||
if isDelayed {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isOrphan {
|
||||
blueScore, err := block.BlueScore()
|
||||
if err != nil {
|
||||
return protocolerrors.Errorf(true, "received an orphan "+
|
||||
"block %s with malformed blue score", blockHash)
|
||||
}
|
||||
|
||||
const maxOrphanBlueScoreDiff = 10000
|
||||
selectedTipBlueScore := flow.DAG().SelectedTipBlueScore()
|
||||
if blueScore > selectedTipBlueScore+maxOrphanBlueScoreDiff {
|
||||
log.Infof("Orphan block %s has blue score %d and the selected tip blue score is "+
|
||||
"%d. Ignoring orphans with a blue score difference from the selected tip greater than %d",
|
||||
blockHash, blueScore, selectedTipBlueScore, maxOrphanBlueScoreDiff)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Request the parents for the orphan block from the peer that sent it.
|
||||
missingAncestors := flow.DAG().GetOrphanMissingAncestorHashes(blockHash)
|
||||
for _, missingAncestor := range missingAncestors {
|
||||
requestQueue.enqueueIfNotExists(missingAncestor)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err = blocklogger.LogBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = flow.Broadcast(appmessage.NewMsgInvBlock(blockHash))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Accepted block %s via relay", blockHash)
|
||||
|
||||
err = flow.StartIBDIfRequired()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flow.StartIBDIfRequired()
|
||||
err = flow.OnNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
import "github.com/kaspanet/kaspad/util/daghash"
|
||||
|
||||
type hashesQueueSet struct {
|
||||
queue []*externalapi.DomainHash
|
||||
set map[externalapi.DomainHash]struct{}
|
||||
queue []*daghash.Hash
|
||||
set map[daghash.Hash]struct{}
|
||||
}
|
||||
|
||||
func (r *hashesQueueSet) enqueueIfNotExists(hash *externalapi.DomainHash) {
|
||||
func (r *hashesQueueSet) enqueueIfNotExists(hash *daghash.Hash) {
|
||||
if _, ok := r.set[*hash]; ok {
|
||||
return
|
||||
}
|
||||
@@ -17,8 +15,8 @@ func (r *hashesQueueSet) enqueueIfNotExists(hash *externalapi.DomainHash) {
|
||||
r.set[*hash] = struct{}{}
|
||||
}
|
||||
|
||||
func (r *hashesQueueSet) dequeue(numItems int) []*externalapi.DomainHash {
|
||||
var hashes []*externalapi.DomainHash
|
||||
func (r *hashesQueueSet) dequeue(numItems int) []*daghash.Hash {
|
||||
var hashes []*daghash.Hash
|
||||
hashes, r.queue = r.queue[:numItems], r.queue[numItems:]
|
||||
for _, hash := range hashes {
|
||||
delete(r.set, *hash)
|
||||
@@ -32,6 +30,6 @@ func (r *hashesQueueSet) len() int {
|
||||
|
||||
func newHashesQueueSet() *hashesQueueSet {
|
||||
return &hashesQueueSet{
|
||||
set: make(map[externalapi.DomainHash]struct{}),
|
||||
set: make(map[daghash.Hash]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,23 @@ package blockrelay
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// SharedRequestedBlocks is a data structure that is shared between peers that
|
||||
// holds the hashes of all the requested blocks to prevent redundant requests.
|
||||
type SharedRequestedBlocks struct {
|
||||
blocks map[externalapi.DomainHash]struct{}
|
||||
blocks map[daghash.Hash]struct{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *SharedRequestedBlocks) remove(hash *externalapi.DomainHash) {
|
||||
func (s *SharedRequestedBlocks) remove(hash *daghash.Hash) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
delete(s.blocks, *hash)
|
||||
}
|
||||
|
||||
func (s *SharedRequestedBlocks) removeSet(blockHashes map[externalapi.DomainHash]struct{}) {
|
||||
func (s *SharedRequestedBlocks) removeSet(blockHashes map[daghash.Hash]struct{}) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for hash := range blockHashes {
|
||||
@@ -27,7 +27,7 @@ func (s *SharedRequestedBlocks) removeSet(blockHashes map[externalapi.DomainHash
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SharedRequestedBlocks) addIfNotExists(hash *externalapi.DomainHash) (exists bool) {
|
||||
func (s *SharedRequestedBlocks) addIfNotExists(hash *daghash.Hash) (exists bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
_, ok := s.blocks[*hash]
|
||||
@@ -41,6 +41,6 @@ func (s *SharedRequestedBlocks) addIfNotExists(hash *externalapi.DomainHash) (ex
|
||||
// NewSharedRequestedBlocks returns a new instance of SharedRequestedBlocks.
|
||||
func NewSharedRequestedBlocks() *SharedRequestedBlocks {
|
||||
return &SharedRequestedBlocks{
|
||||
blocks: make(map[externalapi.DomainHash]struct{}),
|
||||
blocks: make(map[daghash.Hash]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
package handshake
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/locks"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -22,9 +23,9 @@ import (
|
||||
type HandleHandshakeContext interface {
|
||||
Config() *config.Config
|
||||
NetAdapter() *netadapter.NetAdapter
|
||||
Domain() domain.Domain
|
||||
DAG() *blockdag.BlockDAG
|
||||
AddressManager() *addressmanager.AddressManager
|
||||
StartIBDIfRequired() error
|
||||
StartIBDIfRequired()
|
||||
AddToPeers(peer *peerpkg.Peer) error
|
||||
HandleError(err error, flowName string, isStopping *uint32, errChan chan<- error)
|
||||
}
|
||||
@@ -36,12 +37,10 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
) (*peerpkg.Peer, error) {
|
||||
|
||||
// For HandleHandshake to finish, we need to get from the other node
|
||||
// a version and verack messages, so we set doneCount to 2, decrease it
|
||||
// when sending and receiving the version, and close the doneChan when
|
||||
// it's 0. Then we wait for on select for a tick from doneChan or from
|
||||
// errChan.
|
||||
doneCount := int32(2)
|
||||
doneChan := make(chan struct{})
|
||||
// a version and verack messages, so we increase the wait group by 2
|
||||
// and block HandleHandshake with wg.Wait().
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
isStopping := uint32(0)
|
||||
errChan := make(chan error)
|
||||
@@ -56,9 +55,7 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
return
|
||||
}
|
||||
peerAddress = address
|
||||
if atomic.AddInt32(&doneCount, -1) == 0 {
|
||||
close(doneChan)
|
||||
}
|
||||
wg.Done()
|
||||
})
|
||||
|
||||
spawn("HandleHandshake-SendVersion", func() {
|
||||
@@ -67,9 +64,7 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
handleError(err, "SendVersion", &isStopping, errChan)
|
||||
return
|
||||
}
|
||||
if atomic.AddInt32(&doneCount, -1) == 0 {
|
||||
close(doneChan)
|
||||
}
|
||||
wg.Done()
|
||||
})
|
||||
|
||||
select {
|
||||
@@ -78,7 +73,7 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
case <-doneChan:
|
||||
case <-locks.ReceiveFromChanWhenDone(func() { wg.Wait() }):
|
||||
}
|
||||
|
||||
err := context.AddToPeers(peer)
|
||||
@@ -93,10 +88,7 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
context.AddressManager().AddAddresses(peerAddress)
|
||||
}
|
||||
|
||||
err = context.StartIBDIfRequired()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context.StartIBDIfRequired()
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (flow *receiveVersionFlow) start() (*appmessage.NetAddress, error) {
|
||||
}
|
||||
|
||||
// Disconnect from partial nodes in networks that don't allow them
|
||||
if !flow.Config().ActiveNetParams.EnableNonNativeSubnetworks && msgVersion.SubnetworkID != nil {
|
||||
if !flow.DAG().Params.EnableNonNativeSubnetworks && msgVersion.SubnetworkID != nil {
|
||||
return nil, protocolerrors.New(true, "partial nodes are not allowed")
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ func (flow *receiveVersionFlow) start() (*appmessage.NetAddress, error) {
|
||||
isRemoteNodeFull := msgVersion.SubnetworkID == nil
|
||||
isOutbound := flow.peer.Connection().IsOutbound()
|
||||
if (isLocalNodeFull && !isRemoteNodeFull && isOutbound) ||
|
||||
(!isLocalNodeFull && !isRemoteNodeFull && *msgVersion.SubnetworkID != *localSubnetworkID) {
|
||||
(!isLocalNodeFull && !isRemoteNodeFull && !msgVersion.SubnetworkID.IsEqual(localSubnetworkID)) {
|
||||
|
||||
return nil, protocolerrors.New(false, "incompatible subnetworks")
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
)
|
||||
@@ -29,7 +28,6 @@ var (
|
||||
|
||||
type sendVersionFlow struct {
|
||||
HandleHandshakeContext
|
||||
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
peer *peerpkg.Peer
|
||||
}
|
||||
@@ -48,11 +46,7 @@ func SendVersion(context HandleHandshakeContext, incomingRoute *router.Route,
|
||||
}
|
||||
|
||||
func (flow *sendVersionFlow) start() error {
|
||||
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
selectedTipHash := consensusserialization.BlockHash(virtualSelectedParent)
|
||||
selectedTipHash := flow.DAG().SelectedTipHash()
|
||||
subnetworkID := flow.Config().SubnetworkID
|
||||
|
||||
// Version message.
|
||||
@@ -70,7 +64,7 @@ func (flow *sendVersionFlow) start() error {
|
||||
// Advertise if inv messages for transactions are desired.
|
||||
msg.DisableRelayTx = flow.Config().BlocksOnly
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(msg)
|
||||
err := flow.outgoingRoute.Enqueue(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package ibd
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleIBDBlockRequestsContext is the interface for the context needed for the HandleIBDBlockRequests flow.
|
||||
type HandleIBDBlockRequestsContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
// HandleIBDBlockRequests listens to appmessage.MsgRequestRelayBlocks messages and sends
|
||||
// their corresponding blocks to the requesting peer.
|
||||
func HandleIBDBlockRequests(context HandleIBDBlockRequestsContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route) error {
|
||||
|
||||
for {
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgRequestIBDBlocks := message.(*appmessage.MsgRequestIBDBlocks)
|
||||
for _, hash := range msgRequestIBDBlocks.Hashes {
|
||||
// Fetch the block from the database.
|
||||
blockInfo, err := context.Domain().Consensus().GetBlockInfo(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !blockInfo.Exists {
|
||||
return protocolerrors.Errorf(true, "block %s not found", hash)
|
||||
}
|
||||
block, err := context.Domain().Consensus().GetBlock(hash)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
|
||||
}
|
||||
|
||||
// TODO (Partial nodes): Convert block to partial block if needed
|
||||
|
||||
blockMessage := appmessage.DomainBlockToMsgBlock(block)
|
||||
ibdBlockMessage := appmessage.NewMsgIBDBlock(blockMessage)
|
||||
err = outgoingRoute.Enqueue(ibdBlockMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,14 @@ package ibd
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// RequestBlockLocatorContext is the interface for the context needed for the HandleRequestBlockLocator flow.
|
||||
type RequestBlockLocatorContext interface {
|
||||
Domain() domain.Domain
|
||||
DAG() *blockdag.BlockDAG
|
||||
}
|
||||
|
||||
type handleRequestBlockLocatorFlow struct {
|
||||
@@ -37,7 +37,7 @@ func (flow *handleRequestBlockLocatorFlow) start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
locator, err := flow.Domain().Consensus().CreateBlockLocator(lowHash, highHash)
|
||||
locator, err := flow.DAG().BlockLocatorFromHashes(highHash, lowHash)
|
||||
if err != nil || len(locator) == 0 {
|
||||
return protocolerrors.Errorf(true, "couldn't build a block "+
|
||||
"locator between blocks %s and %s", lowHash, highHash)
|
||||
@@ -50,8 +50,8 @@ func (flow *handleRequestBlockLocatorFlow) start() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (lowHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash, err error) {
|
||||
func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (lowHash *daghash.Hash,
|
||||
highHash *daghash.Hash, err error) {
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
@@ -62,7 +62,7 @@ func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (lowHash *ex
|
||||
return msgGetBlockLocator.LowHash, msgGetBlockLocator.HighHash, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlockLocatorFlow) sendBlockLocator(locator externalapi.BlockLocator) error {
|
||||
func (flow *handleRequestBlockLocatorFlow) sendBlockLocator(locator blockdag.BlockLocator) error {
|
||||
msgBlockLocator := appmessage.NewMsgBlockLocator(locator)
|
||||
err := flow.outgoingRoute.Enqueue(msgBlockLocator)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
package ibd
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
const ibdBatchSize = router.DefaultMaxMessages
|
||||
const maxHeaders = appmessage.MaxInvPerMsg
|
||||
|
||||
// RequestIBDBlocksContext is the interface for the context needed for the HandleRequestHeaders flow.
|
||||
type RequestIBDBlocksContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestBlocksFlow struct {
|
||||
RequestIBDBlocksContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestHeaders handles RequestHeaders messages
|
||||
func HandleRequestHeaders(context RequestIBDBlocksContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
||||
flow := &handleRequestBlocksFlow{
|
||||
RequestIBDBlocksContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) start() error {
|
||||
for {
|
||||
lowHash, highHash, err := receiveRequestHeaders(flow.incomingRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgHeaders, err := flow.buildMsgBlockHeaders(lowHash, highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for offset := 0; offset < len(msgHeaders); offset += ibdBatchSize {
|
||||
end := offset + ibdBatchSize
|
||||
if end > len(msgHeaders) {
|
||||
end = len(msgHeaders)
|
||||
}
|
||||
|
||||
blocksToSend := msgHeaders[offset:end]
|
||||
err = flow.sendHeaders(blocksToSend)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exit the loop and don't wait for the GetNextIBDBlocks message if the last batch was
|
||||
// less than ibdBatchSize.
|
||||
if len(blocksToSend) < ibdBatchSize {
|
||||
break
|
||||
}
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := message.(*appmessage.MsgRequestNextHeaders); !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestNextHeaders, message.Command())
|
||||
}
|
||||
}
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receiveRequestHeaders(incomingRoute *router.Route) (lowHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash, err error) {
|
||||
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
msgRequestIBDBlocks := message.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
return msgRequestIBDBlocks.LowHash, msgRequestIBDBlocks.HighHash, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) buildMsgBlockHeaders(lowHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash) ([]*appmessage.MsgBlockHeader, error) {
|
||||
|
||||
blockHashes, err := flow.Domain().Consensus().GetHashesBetween(lowHash, highHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(blockHashes) > maxHeaders {
|
||||
blockHashes = blockHashes[:maxHeaders]
|
||||
}
|
||||
|
||||
msgBlockHeaders := make([]*appmessage.MsgBlockHeader, len(blockHashes))
|
||||
for i, blockHash := range blockHashes {
|
||||
header, err := flow.Domain().Consensus().GetBlockHeader(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgBlockHeaders[i] = appmessage.DomainBlockHeaderToBlockHeader(header)
|
||||
}
|
||||
|
||||
return msgBlockHeaders, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) sendHeaders(headers []*appmessage.MsgBlockHeader) error {
|
||||
for _, msgBlockHeader := range headers {
|
||||
err := flow.outgoingRoute.Enqueue(msgBlockHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package ibd
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMaxHeaders(t *testing.T) {
|
||||
testutils.ForAllNets(t, false, func(t *testing.T, params *dagconfig.Params) {
|
||||
if params.FinalityDepth() > maxHeaders {
|
||||
t.Errorf("FinalityDepth() in %s should be lower or equal to appmessage.MaxInvPerMsg", params.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
126
app/protocol/flows/ibd/handle_request_ibd_blocks.go
Normal file
126
app/protocol/flows/ibd/handle_request_ibd_blocks.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package ibd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
const ibdBatchSize = router.DefaultMaxMessages
|
||||
|
||||
// RequestIBDBlocksContext is the interface for the context needed for the HandleRequestIBDBlocks flow.
|
||||
type RequestIBDBlocksContext interface {
|
||||
DAG() *blockdag.BlockDAG
|
||||
}
|
||||
|
||||
type handleRequestBlocksFlow struct {
|
||||
RequestIBDBlocksContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestIBDBlocks handles getBlocks messages
|
||||
func HandleRequestIBDBlocks(context RequestIBDBlocksContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
||||
flow := &handleRequestBlocksFlow{
|
||||
RequestIBDBlocksContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) start() error {
|
||||
for {
|
||||
lowHash, highHash, err := receiveRequestIBDBlocks(flow.incomingRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgIBDBlocks, err := flow.buildMsgIBDBlocks(lowHash, highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for offset := 0; offset < len(msgIBDBlocks); offset += ibdBatchSize {
|
||||
end := offset + ibdBatchSize
|
||||
if end > len(msgIBDBlocks) {
|
||||
end = len(msgIBDBlocks)
|
||||
}
|
||||
|
||||
blocksToSend := msgIBDBlocks[offset:end]
|
||||
err = flow.sendMsgIBDBlocks(blocksToSend)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exit the loop and don't wait for the GetNextIBDBlocks message if the last batch was
|
||||
// less than ibdBatchSize.
|
||||
if len(blocksToSend) < ibdBatchSize {
|
||||
break
|
||||
}
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := message.(*appmessage.MsgRequestNextIBDBlocks); !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestNextIBDBlocks, message.Command())
|
||||
}
|
||||
}
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgDoneIBDBlocks())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receiveRequestIBDBlocks(incomingRoute *router.Route) (lowHash *daghash.Hash,
|
||||
highHash *daghash.Hash, err error) {
|
||||
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
msgRequestIBDBlocks := message.(*appmessage.MsgRequestIBDBlocks)
|
||||
|
||||
return msgRequestIBDBlocks.LowHash, msgRequestIBDBlocks.HighHash, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) buildMsgIBDBlocks(lowHash *daghash.Hash,
|
||||
highHash *daghash.Hash) ([]*appmessage.MsgIBDBlock, error) {
|
||||
|
||||
const maxHashesInMsgIBDBlocks = appmessage.MaxInvPerMsg
|
||||
blockHashes, err := flow.DAG().AntiPastHashesBetween(lowHash, highHash, maxHashesInMsgIBDBlocks)
|
||||
if err != nil {
|
||||
if errors.Is(err, blockdag.ErrInvalidParameter) {
|
||||
return nil, protocolerrors.Wrapf(true, err, "could not get antiPast between "+
|
||||
"%s and %s", lowHash, highHash)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgIBDBlocks := make([]*appmessage.MsgIBDBlock, len(blockHashes))
|
||||
for i, blockHash := range blockHashes {
|
||||
block, err := flow.DAG().BlockByHash(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgIBDBlocks[i] = appmessage.NewMsgIBDBlock(block.MsgBlock())
|
||||
}
|
||||
|
||||
return msgIBDBlocks, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) sendMsgIBDBlocks(msgIBDBlocks []*appmessage.MsgIBDBlock) error {
|
||||
for _, msgIBDBlock := range msgIBDBlocks {
|
||||
err := flow.outgoingRoute.Enqueue(msgIBDBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package ibd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// HandleRequestIBDRootUTXOSetAndBlockContext is the interface for the context needed for the HandleRequestIBDRootUTXOSetAndBlock flow.
|
||||
type HandleRequestIBDRootUTXOSetAndBlockContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestIBDRootUTXOSetAndBlockFlow struct {
|
||||
HandleRequestIBDRootUTXOSetAndBlockContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestIBDRootUTXOSetAndBlock listens to appmessage.MsgRequestIBDRootUTXOSetAndBlock messages and sends
|
||||
// the IBD root UTXO set and block body.
|
||||
func HandleRequestIBDRootUTXOSetAndBlock(context HandleRequestIBDRootUTXOSetAndBlockContext, incomingRoute,
|
||||
outgoingRoute *router.Route) error {
|
||||
flow := &handleRequestIBDRootUTXOSetAndBlockFlow{
|
||||
HandleRequestIBDRootUTXOSetAndBlockContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestIBDRootUTXOSetAndBlockFlow) start() error {
|
||||
for {
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgRequestIBDRootUTXOSetAndBlock := message.(*appmessage.MsgRequestIBDRootUTXOSetAndBlock)
|
||||
|
||||
utxoSet, err := flow.Domain().Consensus().GetPruningPointUTXOSet(msgRequestIBDRootUTXOSetAndBlock.IBDRoot)
|
||||
if err != nil {
|
||||
if errors.Is(err, ruleerrors.ErrWrongPruningPointHash) {
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgIBDRootNotFound())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
block, err := flow.Domain().Consensus().GetBlock(msgRequestIBDRootUTXOSetAndBlock.IBDRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgIBDRootUTXOSetAndBlock(utxoSet,
|
||||
appmessage.DomainBlockToMsgBlock(block)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,23 @@ package ibd
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/blocklogger"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleIBDContext is the interface for the context needed for the HandleIBD flow.
|
||||
type HandleIBDContext interface {
|
||||
Domain() domain.Domain
|
||||
Config() *config.Config
|
||||
OnNewBlock(block *externalapi.DomainBlock) error
|
||||
FinishIBD() error
|
||||
DAG() *blockdag.BlockDAG
|
||||
OnNewBlock(block *util.Block) error
|
||||
StartIBDIfRequired()
|
||||
FinishIBD()
|
||||
}
|
||||
|
||||
type handleIBDFlow struct {
|
||||
@@ -52,53 +51,8 @@ func (flow *handleIBDFlow) start() error {
|
||||
|
||||
func (flow *handleIBDFlow) runIBD() error {
|
||||
flow.peer.WaitForIBDStart()
|
||||
err := flow.ibdLoop()
|
||||
if err != nil {
|
||||
finishIBDErr := flow.FinishIBD()
|
||||
if finishIBDErr != nil {
|
||||
return finishIBDErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
return flow.FinishIBD()
|
||||
}
|
||||
defer flow.FinishIBD()
|
||||
|
||||
func (flow *handleIBDFlow) ibdLoop() error {
|
||||
for {
|
||||
syncInfo, err := flow.Domain().Consensus().GetSyncInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch syncInfo.State {
|
||||
case externalapi.SyncStateHeadersFirst:
|
||||
err := flow.syncHeaders()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case externalapi.SyncStateMissingUTXOSet:
|
||||
found, err := flow.fetchMissingUTXOSet(syncInfo.IBDRootUTXOBlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
case externalapi.SyncStateMissingBlockBodies:
|
||||
err := flow.syncMissingBlockBodies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case externalapi.SyncStateRelay:
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("unexpected state %s", syncInfo.State)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) syncHeaders() error {
|
||||
peerSelectedTipHash := flow.peer.SelectedTipHash()
|
||||
log.Debugf("Trying to find highest shared chain block with peer %s with selected tip %s", flow.peer, peerSelectedTipHash)
|
||||
highestSharedBlockHash, err := flow.findHighestSharedBlockHash(peerSelectedTipHash)
|
||||
@@ -108,114 +62,13 @@ func (flow *handleIBDFlow) syncHeaders() error {
|
||||
|
||||
log.Debugf("Found highest shared chain block %s with peer %s", highestSharedBlockHash, flow.peer)
|
||||
|
||||
return flow.downloadHeaders(highestSharedBlockHash, peerSelectedTipHash)
|
||||
return flow.downloadBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) syncMissingBlockBodies() error {
|
||||
hashes, err := flow.Domain().Consensus().GetMissingBlockBodyHashes(flow.peer.SelectedTipHash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for offset := 0; offset < len(hashes); offset += appmessage.MaxRequestIBDBlocksHashes {
|
||||
var hashesToRequest []*externalapi.DomainHash
|
||||
if offset+appmessage.MaxRequestIBDBlocksHashes < len(hashes) {
|
||||
hashesToRequest = hashes[offset : offset+appmessage.MaxRequestIBDBlocksHashes]
|
||||
} else {
|
||||
hashesToRequest = hashes[offset:]
|
||||
}
|
||||
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDBlocks(hashesToRequest))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, expectedHash := range hashesToRequest {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgIBDBlock, ok := message.(*appmessage.MsgIBDBlock)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdIBDBlock, message.Command())
|
||||
}
|
||||
|
||||
block := appmessage.MsgBlockToDomainBlock(msgIBDBlock.MsgBlock)
|
||||
blockHash := consensusserialization.BlockHash(block)
|
||||
if *expectedHash != *blockHash {
|
||||
return protocolerrors.Errorf(true, "expected block %s but got %s", expectedHash, blockHash)
|
||||
}
|
||||
|
||||
err = flow.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
return protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "invalid block %s", blockHash)
|
||||
}
|
||||
err = flow.OnNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.DomainHash) (bool, error) {
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootUTXOSetAndBlock(ibdRootHash))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
utxoSet, block, found, err := flow.receiveIBDRootUTXOSetAndBlock()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err = flow.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
blockHash := consensusserialization.BlockHash(block)
|
||||
return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "got invalid block %s during IBD", blockHash)
|
||||
}
|
||||
|
||||
err = flow.Domain().Consensus().SetPruningPointUTXOSet(utxoSet)
|
||||
if err != nil {
|
||||
return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "error with IBD root UTXO set")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) receiveIBDRootUTXOSetAndBlock() ([]byte, *externalapi.DomainBlock, bool, error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgIBDRootUTXOSetAndBlock:
|
||||
return message.UTXOSet,
|
||||
appmessage.MsgBlockToDomainBlock(message.Block), true, nil
|
||||
case *appmessage.MsgIBDRootNotFound:
|
||||
return nil, nil, false, nil
|
||||
default:
|
||||
return nil, nil, false,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s or %s, got: %s",
|
||||
appmessage.CmdIBDRootUTXOSetAndBlock, appmessage.CmdIBDRootNotFound, message.Command(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) findHighestSharedBlockHash(peerSelectedTipHash *externalapi.DomainHash) (lowHash *externalapi.DomainHash,
|
||||
func (flow *handleIBDFlow) findHighestSharedBlockHash(peerSelectedTipHash *daghash.Hash) (lowHash *daghash.Hash,
|
||||
err error) {
|
||||
|
||||
lowHash = flow.Config().ActiveNetParams.GenesisHash
|
||||
lowHash = flow.DAG().Params.GenesisHash
|
||||
highHash := peerSelectedTipHash
|
||||
|
||||
for {
|
||||
@@ -233,28 +86,21 @@ func (flow *handleIBDFlow) findHighestSharedBlockHash(peerSelectedTipHash *exter
|
||||
// If it is, return it. If it isn't, we need to narrow our
|
||||
// getBlockLocator request and try again.
|
||||
locatorHighHash := blockLocatorHashes[0]
|
||||
locatorHighHashInfo, err := flow.Domain().Consensus().GetBlockInfo(locatorHighHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if locatorHighHashInfo.Exists {
|
||||
if flow.DAG().IsInDAG(locatorHighHash) {
|
||||
return locatorHighHash, nil
|
||||
}
|
||||
|
||||
highHash, lowHash, err = flow.Domain().Consensus().FindNextBlockLocatorBoundaries(blockLocatorHashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
highHash, lowHash = flow.DAG().FindNextLocatorBoundaries(blockLocatorHashes)
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) sendGetBlockLocator(lowHash *externalapi.DomainHash, highHash *externalapi.DomainHash) error {
|
||||
func (flow *handleIBDFlow) sendGetBlockLocator(lowHash *daghash.Hash, highHash *daghash.Hash) error {
|
||||
|
||||
msgGetBlockLocator := appmessage.NewMsgRequestBlockLocator(highHash, lowHash)
|
||||
return flow.outgoingRoute.Enqueue(msgGetBlockLocator)
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) receiveBlockLocator() (blockLocatorHashes []*externalapi.DomainHash, err error) {
|
||||
func (flow *handleIBDFlow) receiveBlockLocator() (blockLocatorHashes []*daghash.Hash, err error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -268,17 +114,17 @@ func (flow *handleIBDFlow) receiveBlockLocator() (blockLocatorHashes []*external
|
||||
return msgBlockLocator.BlockLocatorHashes, nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) downloadHeaders(highestSharedBlockHash *externalapi.DomainHash,
|
||||
peerSelectedTipHash *externalapi.DomainHash) error {
|
||||
func (flow *handleIBDFlow) downloadBlocks(highestSharedBlockHash *daghash.Hash,
|
||||
peerSelectedTipHash *daghash.Hash) error {
|
||||
|
||||
err := flow.sendRequestHeaders(highestSharedBlockHash, peerSelectedTipHash)
|
||||
err := flow.sendGetBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blocksReceived := 0
|
||||
for {
|
||||
msgBlockHeader, doneIBD, err := flow.receiveHeader()
|
||||
msgIBDBlock, doneIBD, err := flow.receiveIBDBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -287,14 +133,14 @@ func (flow *handleIBDFlow) downloadHeaders(highestSharedBlockHash *externalapi.D
|
||||
return nil
|
||||
}
|
||||
|
||||
err = flow.processHeader(msgBlockHeader)
|
||||
err = flow.processIBDBlock(msgIBDBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blocksReceived++
|
||||
if blocksReceived%ibdBatchSize == 0 {
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestNextHeaders())
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestNextIBDBlocks())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -302,54 +148,60 @@ func (flow *handleIBDFlow) downloadHeaders(highestSharedBlockHash *externalapi.D
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) sendRequestHeaders(highestSharedBlockHash *externalapi.DomainHash,
|
||||
peerSelectedTipHash *externalapi.DomainHash) error {
|
||||
func (flow *handleIBDFlow) sendGetBlocks(highestSharedBlockHash *daghash.Hash,
|
||||
peerSelectedTipHash *daghash.Hash) error {
|
||||
|
||||
msgGetBlockInvs := appmessage.NewMsgRequstHeaders(highestSharedBlockHash, peerSelectedTipHash)
|
||||
msgGetBlockInvs := appmessage.NewMsgRequstIBDBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
||||
return flow.outgoingRoute.Enqueue(msgGetBlockInvs)
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) receiveHeader() (msgIBDBlock *appmessage.MsgBlockHeader, doneIBD bool, err error) {
|
||||
func (flow *handleIBDFlow) receiveIBDBlock() (msgIBDBlock *appmessage.MsgIBDBlock, doneIBD bool, err error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgBlockHeader:
|
||||
case *appmessage.MsgIBDBlock:
|
||||
return message, false, nil
|
||||
case *appmessage.MsgDoneHeaders:
|
||||
case *appmessage.MsgDoneIBDBlocks:
|
||||
return nil, true, nil
|
||||
default:
|
||||
return nil, false,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s or %s, got: %s", appmessage.CmdHeader, appmessage.CmdDoneHeaders, message.Command())
|
||||
"expected: %s, got: %s", appmessage.CmdIBDBlock, message.Command())
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) processHeader(msgBlockHeader *appmessage.MsgBlockHeader) error {
|
||||
header := appmessage.BlockHeaderToDomainBlockHeader(msgBlockHeader)
|
||||
block := &externalapi.DomainBlock{
|
||||
Header: header,
|
||||
Transactions: nil,
|
||||
func (flow *handleIBDFlow) processIBDBlock(msgIBDBlock *appmessage.MsgIBDBlock) error {
|
||||
block := util.NewBlock(msgIBDBlock.MsgBlock)
|
||||
if flow.DAG().IsInDAG(block.Hash()) {
|
||||
log.Debugf("IBD block %s is already in the DAG. Skipping...", block.Hash())
|
||||
return nil
|
||||
}
|
||||
isOrphan, isDelayed, err := flow.DAG().ProcessBlock(block, blockdag.BFNone)
|
||||
if err != nil {
|
||||
if !errors.As(err, &blockdag.RuleError{}) {
|
||||
return errors.Wrapf(err, "failed to process block %s during IBD", block.Hash())
|
||||
}
|
||||
log.Infof("Rejected block %s from %s during IBD: %s", block.Hash(), flow.peer, err)
|
||||
|
||||
blockHash := consensusserialization.BlockHash(block)
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(blockHash)
|
||||
return protocolerrors.Wrapf(true, err, "got invalid block %s during IBD", block.Hash())
|
||||
}
|
||||
if isOrphan {
|
||||
return protocolerrors.Errorf(true, "received orphan block %s "+
|
||||
"during IBD", block.Hash())
|
||||
}
|
||||
if isDelayed {
|
||||
return protocolerrors.Errorf(false, "received delayed block %s "+
|
||||
"during IBD", block.Hash())
|
||||
}
|
||||
err = flow.OnNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if blockInfo.Exists {
|
||||
log.Debugf("Block header %s is already in the DAG. Skipping...", blockHash)
|
||||
return nil
|
||||
}
|
||||
err = flow.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
err = blocklogger.LogBlock(block)
|
||||
if err != nil {
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
return errors.Wrapf(err, "failed to process header %s during IBD", blockHash)
|
||||
}
|
||||
log.Infof("Rejected block header %s from %s during IBD: %s", blockHash, flow.peer, err)
|
||||
|
||||
return protocolerrors.Wrapf(true, err, "got invalid block %s during IBD", blockHash)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,15 +2,14 @@ package selectedtip
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleRequestSelectedTipContext is the interface for the context needed for the HandleRequestSelectedTip flow.
|
||||
type HandleRequestSelectedTipContext interface {
|
||||
Domain() domain.Domain
|
||||
DAG() *blockdag.BlockDAG
|
||||
}
|
||||
|
||||
type handleRequestSelectedTipFlow struct {
|
||||
@@ -57,10 +56,6 @@ func (flow *handleRequestSelectedTipFlow) receiveGetSelectedTip() error {
|
||||
}
|
||||
|
||||
func (flow *handleRequestSelectedTipFlow) sendSelectedTipHash() error {
|
||||
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgSelectedTip := appmessage.NewMsgSelectedTip(consensusserialization.BlockHash(virtualSelectedParent))
|
||||
msgSelectedTip := appmessage.NewMsgSelectedTip(flow.DAG().SelectedTipHash())
|
||||
return flow.outgoingRoute.Enqueue(msgSelectedTip)
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// RequestSelectedTipContext is the interface for the context needed for the RequestSelectedTip flow.
|
||||
type RequestSelectedTipContext interface {
|
||||
Domain() domain.Domain
|
||||
StartIBDIfRequired() error
|
||||
DAG() *blockdag.BlockDAG
|
||||
StartIBDIfRequired()
|
||||
}
|
||||
|
||||
type requestSelectedTipFlow struct {
|
||||
@@ -59,7 +59,8 @@ func (flow *requestSelectedTipFlow) runSelectedTipRequest() error {
|
||||
}
|
||||
|
||||
flow.peer.SetSelectedTipHash(peerSelectedTipHash)
|
||||
return flow.StartIBDIfRequired()
|
||||
flow.StartIBDIfRequired()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *requestSelectedTipFlow) requestSelectedTip() error {
|
||||
@@ -67,7 +68,7 @@ func (flow *requestSelectedTipFlow) requestSelectedTip() error {
|
||||
return flow.outgoingRoute.Enqueue(msgGetSelectedTip)
|
||||
}
|
||||
|
||||
func (flow *requestSelectedTipFlow) receiveSelectedTip() (selectedTipHash *externalapi.DomainHash, err error) {
|
||||
func (flow *requestSelectedTipFlow) receiveSelectedTip() (selectedTipHash *daghash.Hash, err error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -17,8 +17,9 @@ import (
|
||||
// HandleRelayedTransactions and HandleRequestedTransactions flows.
|
||||
type TransactionsRelayContext interface {
|
||||
NetAdapter() *netadapter.NetAdapter
|
||||
Domain() domain.Domain
|
||||
DAG() *blockdag.BlockDAG
|
||||
SharedRequestedTransactions() *SharedRequestedTransactions
|
||||
TxPool() *mempool.TxPool
|
||||
Broadcast(message appmessage.Message) error
|
||||
OnTransactionAddedToMempool()
|
||||
}
|
||||
@@ -61,9 +62,9 @@ func (flow *handleRelayedTransactionsFlow) start() error {
|
||||
}
|
||||
|
||||
func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
|
||||
inv *appmessage.MsgInvTransaction) (requestedIDs []*externalapi.DomainTransactionID, err error) {
|
||||
inv *appmessage.MsgInvTransaction) (requestedIDs []*daghash.TxID, err error) {
|
||||
|
||||
idsToRequest := make([]*externalapi.DomainTransactionID, 0, len(inv.TxIDs))
|
||||
idsToRequest := make([]*daghash.TxID, 0, len(inv.TxIDs))
|
||||
for _, txID := range inv.TxIDs {
|
||||
if flow.isKnownTransaction(txID) {
|
||||
continue
|
||||
@@ -88,13 +89,29 @@ func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
|
||||
return idsToRequest, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *externalapi.DomainTransactionID) bool {
|
||||
func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *daghash.TxID) bool {
|
||||
// Ask the transaction memory pool if the transaction is known
|
||||
// to it in any form (main pool or orphan).
|
||||
if _, ok := flow.Domain().MiningManager().GetTransaction(txID); ok {
|
||||
if flow.TxPool().HaveTransaction(txID) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if the transaction exists from the point of view of the
|
||||
// DAG's virtual block. Note that this is only a best effort
|
||||
// since it is expensive to check existence of every output and
|
||||
// the only purpose of this check is to avoid downloading
|
||||
// already known transactions. Only the first two outputs are
|
||||
// checked because the vast majority of transactions consist of
|
||||
// two outputs where one is some form of "pay-to-somebody-else"
|
||||
// and the other is a change output.
|
||||
prevOut := appmessage.Outpoint{TxID: *txID}
|
||||
for i := uint32(0); i < 2; i++ {
|
||||
prevOut.Index = i
|
||||
_, ok := flow.DAG().GetUTXOEntry(prevOut)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -118,8 +135,12 @@ func (flow *handleRelayedTransactionsFlow) readInv() (*appmessage.MsgInvTransact
|
||||
return inv, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayedTransactionsFlow) broadcastAcceptedTransactions(acceptedTxIDs []*externalapi.DomainTransactionID) error {
|
||||
inv := appmessage.NewMsgInvTransaction(acceptedTxIDs)
|
||||
func (flow *handleRelayedTransactionsFlow) broadcastAcceptedTransactions(acceptedTxs []*mempool.TxDesc) error {
|
||||
idsToBroadcast := make([]*daghash.TxID, len(acceptedTxs))
|
||||
for i, tx := range acceptedTxs {
|
||||
idsToBroadcast[i] = tx.Tx.ID()
|
||||
}
|
||||
inv := appmessage.NewMsgInvTransaction(idsToBroadcast)
|
||||
return flow.Broadcast(inv)
|
||||
}
|
||||
|
||||
@@ -149,7 +170,7 @@ func (flow *handleRelayedTransactionsFlow) readMsgTxOrNotFound() (
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransactions []*externalapi.DomainTransactionID) error {
|
||||
func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransactions []*daghash.TxID) error {
|
||||
// In case the function returns earlier than expected, we want to make sure sharedRequestedTransactions is
|
||||
// clean from any pending transactions.
|
||||
defer flow.SharedRequestedTransactions().removeMany(requestedTransactions)
|
||||
@@ -159,41 +180,42 @@ func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransact
|
||||
return err
|
||||
}
|
||||
if msgTxNotFound != nil {
|
||||
if msgTxNotFound.ID != expectedID {
|
||||
if !msgTxNotFound.ID.IsEqual(expectedID) {
|
||||
return protocolerrors.Errorf(true, "expected transaction %s, but got %s",
|
||||
expectedID, msgTxNotFound.ID)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
tx := appmessage.MsgTxToDomainTransaction(msgTx)
|
||||
txID := consensusserialization.TransactionID(tx)
|
||||
if txID != expectedID {
|
||||
tx := util.NewTx(msgTx)
|
||||
if !tx.ID().IsEqual(expectedID) {
|
||||
return protocolerrors.Errorf(true, "expected transaction %s, but got %s",
|
||||
expectedID, txID)
|
||||
expectedID, tx.ID())
|
||||
}
|
||||
|
||||
err = flow.Domain().MiningManager().ValidateAndInsertTransaction(tx, true)
|
||||
acceptedTxs, err := flow.TxPool().ProcessTransaction(tx, true)
|
||||
if err != nil {
|
||||
ruleErr := &mempool.RuleError{}
|
||||
if !errors.As(err, ruleErr) {
|
||||
return errors.Wrapf(err, "failed to process transaction %s", txID)
|
||||
return errors.Wrapf(err, "failed to process transaction %s", tx.ID())
|
||||
}
|
||||
|
||||
shouldBan := true
|
||||
shouldBan := false
|
||||
if txRuleErr := (&mempool.TxRuleError{}); errors.As(ruleErr.Err, txRuleErr) {
|
||||
if txRuleErr.RejectCode != mempool.RejectInvalid {
|
||||
shouldBan = false
|
||||
if txRuleErr.RejectCode == mempool.RejectInvalid {
|
||||
shouldBan = true
|
||||
}
|
||||
} else if dagRuleErr := (&blockdag.RuleError{}); errors.As(ruleErr.Err, dagRuleErr) {
|
||||
shouldBan = true
|
||||
}
|
||||
|
||||
if !shouldBan {
|
||||
continue
|
||||
}
|
||||
|
||||
return protocolerrors.Errorf(true, "rejected transaction %s", txID)
|
||||
return protocolerrors.Errorf(true, "rejected transaction %s", tx.ID())
|
||||
}
|
||||
err = flow.broadcastAcceptedTransactions([]*externalapi.DomainTransactionID{txID})
|
||||
err = flow.broadcastAcceptedTransactions(acceptedTxs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (flow *handleRequestedTransactionsFlow) start() error {
|
||||
}
|
||||
|
||||
for _, transactionID := range msgRequestTransactions.IDs {
|
||||
tx, ok := flow.Domain().MiningManager().GetTransaction(transactionID)
|
||||
tx, ok := flow.TxPool().FetchTransaction(transactionID)
|
||||
|
||||
if !ok {
|
||||
msgTransactionNotFound := appmessage.NewMsgTransactionNotFound(transactionID)
|
||||
@@ -41,7 +41,7 @@ func (flow *handleRequestedTransactionsFlow) start() error {
|
||||
continue
|
||||
}
|
||||
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.DomainTransactionToMsgTx(tx))
|
||||
err := flow.outgoingRoute.Enqueue(tx.MsgTx())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
package relaytransactions
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// SharedRequestedTransactions is a data structure that is shared between peers that
|
||||
// holds the IDs of all the requested transactions to prevent redundant requests.
|
||||
type SharedRequestedTransactions struct {
|
||||
transactions map[externalapi.DomainTransactionID]struct{}
|
||||
transactions map[daghash.TxID]struct{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *SharedRequestedTransactions) remove(txID *externalapi.DomainTransactionID) {
|
||||
func (s *SharedRequestedTransactions) remove(txID *daghash.TxID) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
delete(s.transactions, *txID)
|
||||
}
|
||||
|
||||
func (s *SharedRequestedTransactions) removeMany(txIDs []*externalapi.DomainTransactionID) {
|
||||
func (s *SharedRequestedTransactions) removeMany(txIDs []*daghash.TxID) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for _, txID := range txIDs {
|
||||
@@ -27,7 +26,7 @@ func (s *SharedRequestedTransactions) removeMany(txIDs []*externalapi.DomainTran
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SharedRequestedTransactions) addIfNotExists(txID *externalapi.DomainTransactionID) (exists bool) {
|
||||
func (s *SharedRequestedTransactions) addIfNotExists(txID *daghash.TxID) (exists bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
_, ok := s.transactions[*txID]
|
||||
@@ -41,6 +40,6 @@ func (s *SharedRequestedTransactions) addIfNotExists(txID *externalapi.DomainTra
|
||||
// NewSharedRequestedTransactions returns a new instance of SharedRequestedTransactions.
|
||||
func NewSharedRequestedTransactions() *SharedRequestedTransactions {
|
||||
return &SharedRequestedTransactions{
|
||||
transactions: make(map[externalapi.DomainTransactionID]struct{}),
|
||||
transactions: make(map[daghash.TxID]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,15 @@ package protocol
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
// Manager manages the p2p protocol
|
||||
@@ -21,13 +20,13 @@ type Manager struct {
|
||||
}
|
||||
|
||||
// NewManager creates a new instance of the p2p protocol manager
|
||||
func NewManager(cfg *config.Config, domain domain.Domain, netAdapter *netadapter.NetAdapter, addressManager *addressmanager.AddressManager,
|
||||
func NewManager(cfg *config.Config, dag *blockdag.BlockDAG, netAdapter *netadapter.NetAdapter,
|
||||
addressManager *addressmanager.AddressManager, txPool *mempool.TxPool,
|
||||
connectionManager *connmanager.ConnectionManager) (*Manager, error) {
|
||||
|
||||
manager := Manager{
|
||||
context: flowcontext.New(cfg, domain, addressManager, netAdapter, connectionManager),
|
||||
context: flowcontext.New(cfg, dag, addressManager, txPool, netAdapter, connectionManager),
|
||||
}
|
||||
|
||||
netAdapter.SetP2PRouterInitializer(manager.routerInitializer)
|
||||
return &manager, nil
|
||||
}
|
||||
@@ -44,13 +43,13 @@ func (m *Manager) IBDPeer() *peerpkg.Peer {
|
||||
}
|
||||
|
||||
// AddTransaction adds transaction to the mempool and propagates it.
|
||||
func (m *Manager) AddTransaction(tx *externalapi.DomainTransaction) error {
|
||||
func (m *Manager) AddTransaction(tx *util.Tx) error {
|
||||
return m.context.AddTransaction(tx)
|
||||
}
|
||||
|
||||
// AddBlock adds the given block to the DAG and propagates it.
|
||||
func (m *Manager) AddBlock(block *externalapi.DomainBlock) error {
|
||||
return m.context.AddBlock(block)
|
||||
func (m *Manager) AddBlock(block *util.Block, flags blockdag.BehaviorFlags) error {
|
||||
return m.context.AddBlock(block, flags)
|
||||
}
|
||||
|
||||
func (m *Manager) runFlows(flows []*flow, peer *peerpkg.Peer, errChan <-chan error) error {
|
||||
|
||||
@@ -1,36 +1,32 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
mathUtil "github.com/kaspanet/kaspad/util/math"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// maxProtocolVersion version is the maximum supported protocol
|
||||
// version this kaspad node supports
|
||||
const maxProtocolVersion = 1
|
||||
|
||||
// Peer holds data about a peer.
|
||||
type Peer struct {
|
||||
connection *netadapter.NetConnection
|
||||
|
||||
selectedTipHashMtx sync.RWMutex
|
||||
selectedTipHash *externalapi.DomainHash
|
||||
selectedTipHash *daghash.Hash
|
||||
|
||||
userAgent string
|
||||
services appmessage.ServiceFlag
|
||||
advertisedProtocolVerion uint32 // protocol version advertised by remote
|
||||
protocolVersion uint32 // negotiated protocol version
|
||||
disableRelayTx bool
|
||||
subnetworkID *externalapi.DomainSubnetworkID
|
||||
subnetworkID *subnetworkid.SubnetworkID
|
||||
|
||||
timeOffset time.Duration
|
||||
connectionStarted time.Time
|
||||
@@ -63,14 +59,14 @@ func (p *Peer) Connection() *netadapter.NetConnection {
|
||||
}
|
||||
|
||||
// SelectedTipHash returns the selected tip of the peer.
|
||||
func (p *Peer) SelectedTipHash() *externalapi.DomainHash {
|
||||
func (p *Peer) SelectedTipHash() *daghash.Hash {
|
||||
p.selectedTipHashMtx.RLock()
|
||||
defer p.selectedTipHashMtx.RUnlock()
|
||||
return p.selectedTipHash
|
||||
}
|
||||
|
||||
// SetSelectedTipHash sets the selected tip of the peer.
|
||||
func (p *Peer) SetSelectedTipHash(hash *externalapi.DomainHash) {
|
||||
func (p *Peer) SetSelectedTipHash(hash *daghash.Hash) {
|
||||
p.selectedTipHashMtx.Lock()
|
||||
defer p.selectedTipHashMtx.Unlock()
|
||||
p.selectedTipHash = hash
|
||||
@@ -78,7 +74,7 @@ func (p *Peer) SetSelectedTipHash(hash *externalapi.DomainHash) {
|
||||
|
||||
// SubnetworkID returns the subnetwork the peer is associated with.
|
||||
// It is nil in full nodes.
|
||||
func (p *Peer) SubnetworkID() *externalapi.DomainSubnetworkID {
|
||||
func (p *Peer) SubnetworkID() *subnetworkid.SubnetworkID {
|
||||
return p.subnetworkID
|
||||
}
|
||||
|
||||
@@ -116,9 +112,9 @@ func (p *Peer) IsOutbound() bool {
|
||||
func (p *Peer) UpdateFieldsFromMsgVersion(msg *appmessage.MsgVersion) {
|
||||
// Negotiate the protocol version.
|
||||
p.advertisedProtocolVerion = msg.ProtocolVersion
|
||||
p.protocolVersion = mathUtil.MinUint32(maxProtocolVersion, p.advertisedProtocolVerion)
|
||||
p.protocolVersion = mathUtil.MinUint32(p.protocolVersion, p.advertisedProtocolVerion)
|
||||
log.Debugf("Negotiated protocol version %d for peer %s",
|
||||
p.protocolVersion, p)
|
||||
p.protocolVersion, p.ID())
|
||||
|
||||
// Set the supported services for the peer to what the remote peer
|
||||
// advertised.
|
||||
|
||||
@@ -50,9 +50,15 @@ func (m *Manager) routerInitializer(router *routerpkg.Router, netConnection *net
|
||||
return
|
||||
}
|
||||
|
||||
netConnection.SetOnInvalidMessageHandler(func(err error) {
|
||||
if atomic.AddUint32(&isStopping, 1) == 1 {
|
||||
errChan <- protocolerrors.Wrap(true, err, "received bad message")
|
||||
spawn("Manager.routerInitializer-netConnection.DequeueInvalidMessage", func() {
|
||||
for {
|
||||
isOpen, err := netConnection.DequeueInvalidMessage()
|
||||
if !isOpen {
|
||||
return
|
||||
}
|
||||
if atomic.AddUint32(&isStopping, 1) == 1 {
|
||||
errChan <- protocolerrors.Wrap(true, err, "received bad message")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -118,7 +124,7 @@ func (m *Manager) registerAddressFlows(router *routerpkg.Router, isStopping *uin
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*flow{
|
||||
m.registerFlow("SendAddresses", router, []appmessage.MessageCommand{appmessage.CmdRequestAddresses}, isStopping, errChan,
|
||||
m.registerOneTimeFlow("SendAddresses", router, []appmessage.MessageCommand{appmessage.CmdRequestAddresses}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return addressexchange.SendAddresses(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
@@ -174,52 +180,33 @@ func (m *Manager) registerIBDFlows(router *routerpkg.Router, isStopping *uint32,
|
||||
|
||||
return []*flow{
|
||||
m.registerFlow("HandleIBD", router, []appmessage.MessageCommand{appmessage.CmdBlockLocator, appmessage.CmdIBDBlock,
|
||||
appmessage.CmdDoneHeaders, appmessage.CmdIBDRootNotFound, appmessage.CmdIBDRootUTXOSetAndBlock, appmessage.CmdHeader},
|
||||
isStopping, errChan,
|
||||
appmessage.CmdDoneIBDBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ibd.HandleIBD(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("RequestSelectedTip", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdSelectedTip}, isStopping, errChan,
|
||||
m.registerFlow("RequestSelectedTip", router, []appmessage.MessageCommand{appmessage.CmdSelectedTip}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return selectedtip.RequestSelectedTip(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestSelectedTip", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestSelectedTip}, isStopping, errChan,
|
||||
m.registerFlow("HandleRequestSelectedTip", router, []appmessage.MessageCommand{appmessage.CmdRequestSelectedTip}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return selectedtip.HandleRequestSelectedTip(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestBlockLocator", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestBlockLocator}, isStopping, errChan,
|
||||
m.registerFlow("HandleRequestBlockLocator", router, []appmessage.MessageCommand{appmessage.CmdRequestBlockLocator}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ibd.HandleRequestBlockLocator(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestHeaders", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestHeaders, appmessage.CmdRequestNextHeaders}, isStopping, errChan,
|
||||
m.registerFlow("HandleRequestIBDBlocks", router, []appmessage.MessageCommand{appmessage.CmdRequestIBDBlocks, appmessage.CmdRequestNextIBDBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ibd.HandleRequestHeaders(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestIBDRootUTXOSetAndBlock", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestIBDRootUTXOSetAndBlock}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ibd.HandleRequestIBDRootUTXOSetAndBlock(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleIBDBlockRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestIBDBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ibd.HandleIBDBlockRequests(m.context, incomingRoute, outgoingRoute)
|
||||
return ibd.HandleRequestIBDBlocks(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package protocolerrors
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
// ProtocolError is an error that signifies a violation
|
||||
// of the peer-to-peer protocol
|
||||
@@ -53,14 +50,3 @@ func Wrapf(shouldBan bool, err error, format string, args ...interface{}) error
|
||||
Cause: errors.Wrapf(err, format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertToBanningProtocolErrorIfRuleError converts the given error to
|
||||
// a banning protocol error if it's a rule error, and otherwise keep it
|
||||
// as is.
|
||||
func ConvertToBanningProtocolErrorIfRuleError(err error, format string, args ...interface{}) error {
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
return err
|
||||
}
|
||||
|
||||
return Wrapf(true, err, format, args...)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ 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"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"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
|
||||
@@ -20,21 +24,27 @@ type Manager struct {
|
||||
// NewManager creates a new RPC Manager
|
||||
func NewManager(
|
||||
cfg *config.Config,
|
||||
domain domain.Domain,
|
||||
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,
|
||||
domain,
|
||||
netAdapter,
|
||||
dag,
|
||||
protocolManager,
|
||||
connectionManager,
|
||||
blockTemplateGenerator,
|
||||
mempool,
|
||||
addressManager,
|
||||
acceptanceIndex,
|
||||
shutDownChan,
|
||||
),
|
||||
}
|
||||
@@ -44,11 +54,27 @@ func NewManager(
|
||||
}
|
||||
|
||||
// NotifyBlockAddedToDAG notifies the manager that a block has been added to the DAG
|
||||
func (m *Manager) NotifyBlockAddedToDAG(block *externalapi.DomainBlock) error {
|
||||
notification := appmessage.NewBlockAddedNotificationMessage(appmessage.DomainBlockToMsgBlock(block))
|
||||
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)
|
||||
@@ -60,3 +86,8 @@ 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()
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ var handlers = map[appmessage.MessageCommand]handler{
|
||||
appmessage.CmdResolveFinalityConflictRequestMessage: rpchandlers.HandleResolveFinalityConflict,
|
||||
appmessage.CmdNotifyFinalityConflictsRequestMessage: rpchandlers.HandleNotifyFinalityConflicts,
|
||||
appmessage.CmdGetMempoolEntriesRequestMessage: rpchandlers.HandleGetMempoolEntries,
|
||||
appmessage.CmdShutDownRequestMessage: rpchandlers.HandleShutDown,
|
||||
appmessage.CmdShutDownRequestMessage: rpchandlers.HandleGetMempoolEntries,
|
||||
appmessage.CmdGetHeadersRequestMessage: rpchandlers.HandleGetHeaders,
|
||||
}
|
||||
|
||||
|
||||
476
app/rpc/rpccontext/blocktemplate.go
Normal file
476
app/rpc/rpccontext/blocktemplate.go
Normal file
@@ -0,0 +1,476 @@
|
||||
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
|
||||
}
|
||||
40
app/rpc/rpccontext/chainblocks.go
Normal file
40
app/rpc/rpccontext/chainblocks.go
Normal file
@@ -0,0 +1,40 @@
|
||||
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
|
||||
}
|
||||
@@ -2,7 +2,10 @@ package rpccontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"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"
|
||||
@@ -11,35 +14,46 @@ import (
|
||||
|
||||
// Context represents the RPC context
|
||||
type Context struct {
|
||||
Config *config.Config
|
||||
NetAdapter *netadapter.NetAdapter
|
||||
Domain domain.Domain
|
||||
ProtocolManager *protocol.Manager
|
||||
ConnectionManager *connmanager.ConnectionManager
|
||||
AddressManager *addressmanager.AddressManager
|
||||
ShutDownChan chan<- 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,
|
||||
domain domain.Domain,
|
||||
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,
|
||||
Domain: domain,
|
||||
ProtocolManager: protocolManager,
|
||||
ConnectionManager: connectionManager,
|
||||
AddressManager: addressManager,
|
||||
ShutDownChan: shutDownChan,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,32 +1,57 @@
|
||||
package rpccontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/blocks"
|
||||
"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"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/pointers"
|
||||
)
|
||||
|
||||
// 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 *externalapi.DomainBlock, includeTransactionVerboseData bool) (*appmessage.BlockVerboseData, error) {
|
||||
hash := consensusserialization.BlockHash(block)
|
||||
blockHeader := block.Header
|
||||
func (ctx *Context) BuildBlockVerboseData(block *util.Block, includeTransactionVerboseData bool) (*appmessage.BlockVerboseData, error) {
|
||||
hash := block.Hash()
|
||||
params := ctx.DAG.Params
|
||||
blockHeader := block.MsgBlock().Header
|
||||
|
||||
blueScore, err := blocks.ExtractBlueScore(block)
|
||||
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
|
||||
}
|
||||
@@ -38,25 +63,33 @@ func (ctx *Context) BuildBlockVerboseData(block *externalapi.DomainBlock, includ
|
||||
HashMerkleRoot: blockHeader.HashMerkleRoot.String(),
|
||||
AcceptedIDMerkleRoot: blockHeader.AcceptedIDMerkleRoot.String(),
|
||||
UTXOCommitment: blockHeader.UTXOCommitment.String(),
|
||||
ParentHashes: externalapi.DomainHashesToStrings(blockHeader.ParentHashes),
|
||||
ParentHashes: daghash.Strings(blockHeader.ParentHashes),
|
||||
SelectedParentHash: selectedParentHashStr,
|
||||
Nonce: blockHeader.Nonce,
|
||||
Time: blockHeader.TimeInMilliseconds,
|
||||
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, ctx.Config.ActiveNetParams),
|
||||
BlueScore: blueScore,
|
||||
Difficulty: ctx.GetDifficultyRatio(blockHeader.Bits, params),
|
||||
ChildHashes: daghash.Strings(childHashes),
|
||||
AcceptedBlockHashes: daghash.Strings(acceptedBlockHashes),
|
||||
}
|
||||
|
||||
txIDs := make([]string, len(block.Transactions))
|
||||
for i, tx := range block.Transactions {
|
||||
txIDs[i] = consensusserialization.TransactionID(tx).String()
|
||||
transactions := block.Transactions()
|
||||
txIDs := make([]string, len(transactions))
|
||||
for i, tx := range transactions {
|
||||
txIDs[i] = tx.ID().String()
|
||||
}
|
||||
result.TxIDs = txIDs
|
||||
|
||||
if includeTransactionVerboseData {
|
||||
transactionVerboseData := make([]*appmessage.TransactionVerboseData, len(block.Transactions))
|
||||
for i, tx := range block.Transactions {
|
||||
txID := consensusserialization.TransactionID(tx).String()
|
||||
data, err := ctx.BuildTransactionVerboseData(tx, txID, blockHeader, hash.String())
|
||||
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
|
||||
}
|
||||
@@ -89,53 +122,76 @@ func (ctx *Context) GetDifficultyRatio(bits uint32, params *dagconfig.Params) fl
|
||||
|
||||
// BuildTransactionVerboseData builds a TransactionVerboseData from
|
||||
// the given parameters
|
||||
func (ctx *Context) BuildTransactionVerboseData(tx *externalapi.DomainTransaction, txID string,
|
||||
blockHeader *externalapi.DomainBlockHeader, blockHash string) (
|
||||
*appmessage.TransactionVerboseData, error) {
|
||||
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 tx.SubnetworkID != subnetworks.SubnetworkIDNative {
|
||||
payloadHash = tx.PayloadHash.String()
|
||||
if mtx.PayloadHash != nil {
|
||||
payloadHash = mtx.PayloadHash.String()
|
||||
}
|
||||
|
||||
txReply := &appmessage.TransactionVerboseData{
|
||||
Hex: mtxHex,
|
||||
TxID: txID,
|
||||
Hash: consensusserialization.TransactionHash(tx).String(),
|
||||
Size: estimatedsize.TransactionEstimatedSerializedSize(tx),
|
||||
TransactionVerboseInputs: ctx.buildTransactionVerboseInputs(tx),
|
||||
TransactionVerboseOutputs: ctx.buildTransactionVerboseOutputs(tx, nil),
|
||||
Version: tx.Version,
|
||||
LockTime: tx.LockTime,
|
||||
SubnetworkID: tx.SubnetworkID.String(),
|
||||
Gas: tx.Gas,
|
||||
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(tx.Payload),
|
||||
Payload: hex.EncodeToString(mtx.Payload),
|
||||
}
|
||||
|
||||
if blockHeader != nil {
|
||||
txReply.Time = uint64(blockHeader.TimeInMilliseconds)
|
||||
txReply.BlockTime = uint64(blockHeader.TimeInMilliseconds)
|
||||
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
|
||||
}
|
||||
|
||||
func (ctx *Context) buildTransactionVerboseInputs(tx *externalapi.DomainTransaction) []*appmessage.TransactionVerboseInput {
|
||||
inputs := make([]*appmessage.TransactionVerboseInput, len(tx.Inputs))
|
||||
for i, transactionInput := range tx.Inputs {
|
||||
// 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(transactionInput.SignatureScript)
|
||||
disbuf, _ := txscript.DisasmString(txIn.SignatureScript)
|
||||
|
||||
input := &appmessage.TransactionVerboseInput{}
|
||||
input.TxID = transactionInput.PreviousOutpoint.TransactionID.String()
|
||||
input.OutputIndex = transactionInput.PreviousOutpoint.Index
|
||||
input.Sequence = transactionInput.Sequence
|
||||
input.TxID = txIn.PreviousOutpoint.TxID.String()
|
||||
input.OutputIndex = txIn.PreviousOutpoint.Index
|
||||
input.Sequence = txIn.Sequence
|
||||
input.ScriptSig = &appmessage.ScriptSig{
|
||||
Asm: disbuf,
|
||||
Hex: hex.EncodeToString(transactionInput.SignatureScript),
|
||||
Hex: hex.EncodeToString(txIn.SignatureScript),
|
||||
}
|
||||
inputs[i] = input
|
||||
}
|
||||
@@ -145,18 +201,18 @@ func (ctx *Context) buildTransactionVerboseInputs(tx *externalapi.DomainTransact
|
||||
|
||||
// buildTransactionVerboseOutputs returns a slice of JSON objects for the outputs of the passed
|
||||
// transaction.
|
||||
func (ctx *Context) buildTransactionVerboseOutputs(tx *externalapi.DomainTransaction, filterAddrMap map[string]struct{}) []*appmessage.TransactionVerboseOutput {
|
||||
outputs := make([]*appmessage.TransactionVerboseOutput, len(tx.Outputs))
|
||||
for i, transactionOutput := range tx.Outputs {
|
||||
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(transactionOutput.ScriptPublicKey)
|
||||
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(
|
||||
transactionOutput.ScriptPublicKey, ctx.Config.ActiveNetParams)
|
||||
v.ScriptPubKey, ctx.DAG.Params)
|
||||
|
||||
// Encode the addresses while checking if the address passes the
|
||||
// filter when needed.
|
||||
@@ -178,11 +234,11 @@ func (ctx *Context) buildTransactionVerboseOutputs(tx *externalapi.DomainTransac
|
||||
|
||||
output := &appmessage.TransactionVerboseOutput{}
|
||||
output.Index = uint32(i)
|
||||
output.Value = transactionOutput.Value
|
||||
output.Value = v.Value
|
||||
output.ScriptPubKey = &appmessage.ScriptPubKeyResult{
|
||||
Address: encodedAddr,
|
||||
Asm: disbuf,
|
||||
Hex: hex.EncodeToString(transactionOutput.ScriptPublicKey),
|
||||
Hex: hex.EncodeToString(v.ScriptPubKey),
|
||||
Type: scriptClass.String(),
|
||||
}
|
||||
outputs[i] = output
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
// 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.Config.ActiveNetParams.DefaultPort)
|
||||
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)
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package rpchandlers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
"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
|
||||
@@ -12,27 +16,83 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
|
||||
getBlockRequest := request.(*appmessage.GetBlockRequestMessage)
|
||||
|
||||
// Load the raw block bytes from the database.
|
||||
hash, err := hashes.FromString(getBlockRequest.Hash)
|
||||
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
|
||||
}
|
||||
|
||||
block, err := context.Domain.Consensus().GetBlock(hash)
|
||||
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
|
||||
}
|
||||
|
||||
response := appmessage.NewGetBlockResponseMessage()
|
||||
|
||||
blockVerboseData, err := context.BuildBlockVerboseData(block, getBlockRequest.IncludeTransactionVerboseData)
|
||||
blockBytes, err := block.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.BlockVerboseData = blockVerboseData
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@ import (
|
||||
|
||||
// HandleGetBlockCount handles the respectively named RPC command
|
||||
func HandleGetBlockCount(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
|
||||
syncInfo, err := context.Domain.Consensus().GetSyncInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := appmessage.NewGetBlockCountResponseMessage(syncInfo.BlockCount)
|
||||
response := appmessage.NewGetBlockCountResponseMessage(context.DAG.BlockCount())
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -4,14 +4,20 @@ 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) {
|
||||
params := context.Config.ActiveNetParams
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package rpchandlers
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
@@ -13,25 +11,76 @@ import (
|
||||
func HandleGetBlockTemplate(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
|
||||
getBlockTemplateRequest := request.(*appmessage.GetBlockTemplateRequestMessage)
|
||||
|
||||
payAddress, err := util.DecodeAddress(getBlockTemplateRequest.PayAddress, context.Config.ActiveNetParams.Prefix)
|
||||
payAddress, err := util.DecodeAddress(getBlockTemplateRequest.PayAddress, context.DAG.Params.Prefix)
|
||||
if err != nil {
|
||||
errorMessage := &appmessage.GetBlockResponseMessage{}
|
||||
errorMessage := &appmessage.GetBlockTemplateResponseMessage{}
|
||||
errorMessage.Error = appmessage.RPCErrorf("Could not decode address: %s", err)
|
||||
return errorMessage, nil
|
||||
}
|
||||
|
||||
scriptPublicKey, err := txscript.PayToAddrScript(payAddress)
|
||||
// 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
|
||||
}
|
||||
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{ScriptPublicKey: scriptPublicKey}
|
||||
|
||||
templateBlock, err := context.Domain.MiningManager().GetBlockTemplate(coinbaseData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgBlock := appmessage.DomainBlockToMsgBlock(templateBlock)
|
||||
|
||||
return appmessage.NewGetBlockTemplateResponseMessage(msgBlock), nil
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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 (
|
||||
@@ -14,7 +17,102 @@ const (
|
||||
|
||||
// HandleGetBlocks handles the respectively named RPC command
|
||||
func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
|
||||
response := &appmessage.GetBlocksResponseMessage{}
|
||||
response.Error = appmessage.RPCErrorf("not implemented")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ 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 (
|
||||
@@ -14,7 +16,91 @@ const (
|
||||
|
||||
// HandleGetChainFromBlock handles the respectively named RPC command
|
||||
func HandleGetChainFromBlock(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
|
||||
response := &appmessage.GetChainFromBlockResponseMessage{}
|
||||
response.Error = appmessage.RPCErrorf("not implemented")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -8,6 +8,6 @@ import (
|
||||
|
||||
// HandleGetCurrentNetwork handles the respectively named RPC command
|
||||
func HandleGetCurrentNetwork(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
|
||||
response := appmessage.NewGetCurrentNetworkResponseMessage(context.Config.ActiveNetParams.Net.String())
|
||||
response := appmessage.NewGetCurrentNetworkResponseMessage(context.DAG.Params.Net.String())
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -1,14 +1,52 @@
|
||||
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) {
|
||||
response := &appmessage.GetHeadersResponseMessage{}
|
||||
response.Error = appmessage.RPCErrorf("not implemented")
|
||||
return response, nil
|
||||
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
|
||||
}
|
||||
|
||||
@@ -8,7 +8,18 @@ import (
|
||||
|
||||
// HandleGetMempoolEntries handles the respectively named RPC command
|
||||
func HandleGetMempoolEntries(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
|
||||
response := &appmessage.GetMempoolEntriesResponseMessage{}
|
||||
response.Error = appmessage.RPCErrorf("not implemented")
|
||||
return response, nil
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,9 +4,32 @@ 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) {
|
||||
return &appmessage.GetMempoolEntryResponseMessage{}, nil
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,19 +3,11 @@ package rpchandlers
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"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) {
|
||||
selectedTip, err := context.Domain.Consensus().GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := appmessage.NewGetSelectedTipHashResponseMessage(
|
||||
consensusserialization.BlockHash(selectedTip).String())
|
||||
|
||||
response := appmessage.NewGetSelectedTipHashResponseMessage(context.DAG.SelectedTipHash().String())
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -4,11 +4,31 @@ 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) {
|
||||
response := &appmessage.GetSubnetworkResponseMessage{}
|
||||
response.Error = appmessage.RPCErrorf("not implemented")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ import (
|
||||
|
||||
// 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
|
||||
|
||||
@@ -4,11 +4,27 @@ 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) {
|
||||
response := &appmessage.ResolveFinalityConflictResponseMessage{}
|
||||
response.Error = appmessage.RPCErrorf("not implemented")
|
||||
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
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user