Skip to content

Commit 054807c

Browse files
dev-warrior777martonp
authored andcommitted
client: Deserialize Firo blocks from wire bytes (#3263)
* client: Deserialize Firo blocks from wire bytes De-serialize: - header: for either regtest, mainnet or testnet - minimally: all Firo transaction types to reach the start of the next transaction in a block stream. - fully: Normal transparent transactions Return de-serialized header and any Normal transparent transactions as wire.MsgTx's * client,test: Fix regnet test * client: Fix TransactionSpork TransactionSpork, TransactionQuorumCommitment both have zero transaction inputs or outputs. * client: Fix TransactionAlias TRANSACTION_ALIAS is a regular spark spend transaction, but contains additional info, it is a spark name transaction. In this tx besides spark spend outputs you also have data about created/modified spark name. * client/docs: New Firo version "This mandatory release introduces Spark Names, allowing users to register unique, easy-to-remember aliases instead of long cryptographic addresses." https://github.com/firoorg/firo/releases/tag/v0.14.14.1 * client: Fix testnet typo and clean up comments * client: Update based on review from JoeGruffins * client: Update on review from MartonP --------- Co-authored-by: dev-warrior777 <>
1 parent 44c244d commit 054807c

File tree

4 files changed

+434
-11
lines changed

4 files changed

+434
-11
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package firo
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/btcsuite/btcd/chaincfg"
9+
"github.com/btcsuite/btcd/chaincfg/chainhash"
10+
"github.com/btcsuite/btcd/wire"
11+
)
12+
13+
const (
14+
// Only blocks mined with progpow are considered.
15+
// Previous mining algorithms: MTP and Lyra2z ignored as being too early for
16+
// Firo wallet on Dex ~ late 2023
17+
ProgpowStartTime = 1635228000 // Tue Oct 26 2021 06:00:00 UTC+0
18+
HeaderLength = 80
19+
ProgpowExtraLength = 40
20+
ProgpowFullHeaderLength = HeaderLength + ProgpowExtraLength
21+
)
22+
23+
var errInvalidBlockLength = errors.New("invalid block length")
24+
25+
// deserializeBlock deserializes the wire bytes passed in as blk and returns the
26+
// header for the network plus any Transparent transactions found parsed into a
27+
// wire.MsgBlock.
28+
//
29+
// Other transaction types are discarded; including coinbase.
30+
func deserializeBlock(net *chaincfg.Params, blk []byte) (*wire.MsgBlock, error) {
31+
var hdrHash chainhash.Hash
32+
var hdr *wire.BlockHeader
33+
34+
// hash header
35+
var header []byte
36+
switch net.Name {
37+
case "mainnet", "testnet3", "testnet":
38+
if len(blk) < ProgpowFullHeaderLength {
39+
return nil, errInvalidBlockLength
40+
}
41+
header = make([]byte, ProgpowFullHeaderLength)
42+
copy(header, blk[:ProgpowFullHeaderLength])
43+
44+
case "regtest":
45+
if len(blk) < HeaderLength {
46+
return nil, errInvalidBlockLength
47+
}
48+
header = make([]byte, HeaderLength)
49+
copy(header, blk[:HeaderLength])
50+
51+
default:
52+
return nil, fmt.Errorf("unknown net: %s", net.Name)
53+
}
54+
hdrHash = chainhash.DoubleHashH(header)
55+
56+
// make a reader over the full block
57+
r := bytes.NewReader(blk)
58+
59+
// deserialize the first 80 bytes of the header
60+
hdr = &wire.BlockHeader{}
61+
err := hdr.Deserialize(r)
62+
if err != nil {
63+
return nil, fmt.Errorf("failed to deserialize block header: %w", err)
64+
}
65+
66+
if int(hdr.Timestamp.Unix()) < ProgpowStartTime {
67+
return nil, fmt.Errorf("dex not considering blocks mined before progpow")
68+
}
69+
70+
if net.Name != "regtest" {
71+
// Blocks mined later than progpow start time have 40 extra bytes holding
72+
// mining info. Skip over!
73+
var extraBytes = make([]byte, ProgpowExtraLength)
74+
r.Read(extraBytes)
75+
}
76+
77+
// This block's transactions
78+
txnCount, err := wire.ReadVarInt(r, 0)
79+
if err != nil {
80+
return nil, fmt.Errorf("failed to parse transaction count: %w", err)
81+
}
82+
83+
if txnCount == 0 {
84+
return nil, fmt.Errorf("invalid transaction count 0 -- must at least have a coinbase")
85+
}
86+
87+
txns := make([]*wire.MsgTx, 0, txnCount)
88+
for i := 0; i < int(txnCount); i++ {
89+
tx, err := deserializeTransaction(r)
90+
91+
if err != nil {
92+
return nil, fmt.Errorf("failed to deserialize transaction %d of %d (type=%d) in block %s: %w",
93+
i+1, txnCount, tx.txType, hdrHash.String(), err)
94+
}
95+
96+
if tx.txType == TransactionNormal {
97+
txns = append(txns, tx.msgTx)
98+
}
99+
}
100+
101+
return &wire.MsgBlock{
102+
Header: *hdr,
103+
Transactions: txns,
104+
}, nil
105+
}

client/asset/firo/firo.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ import (
2222
"github.com/btcsuite/btcd/btcec/v2"
2323
"github.com/btcsuite/btcd/btcutil"
2424
"github.com/btcsuite/btcd/chaincfg"
25+
"github.com/btcsuite/btcd/wire"
2526
)
2627

2728
const (
2829
version = 0
2930
// Zcoin XZC
3031
BipID = 136
31-
// Consensus changes v0.14.14.0
32-
minNetworkVersion = 141400
32+
// https://github.com/firoorg/firo/releases/tag/v0.14.14.1
33+
minNetworkVersion = 141401
3334
walletTypeRPC = "firodRPC"
3435
walletTypeElectrum = "electrumRPC"
3536
estimateFeeConfs = 2 // 2 blocks should be enough
@@ -137,13 +138,6 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network)
137138
return nil, fmt.Errorf("unknown network ID %v", network)
138139
}
139140

140-
// Designate the clone ports.
141-
ports := dexbtc.NetPorts{
142-
Mainnet: "8888",
143-
Testnet: "18888",
144-
Simnet: "28888",
145-
}
146-
147141
cloneCFG := &btc.BTCCloneCFG{
148142
WalletCFG: cfg,
149143
MinNetworkVersion: minNetworkVersion,
@@ -152,7 +146,7 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network)
152146
Logger: logger,
153147
Network: network,
154148
ChainParams: params,
155-
Ports: ports,
149+
Ports: dexfiro.NetPorts,
156150
DefaultFallbackFee: dexfiro.DefaultFee,
157151
DefaultFeeRateLimit: dexfiro.DefaultFeeRateLimit,
158152
Segwit: false,
@@ -172,15 +166,18 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network)
172166
ExternalFeeEstimator: externalFeeRate,
173167
AddressDecoder: decodeAddress,
174168
PrivKeyFunc: nil, // set only for walletTypeRPC below
169+
BlockDeserializer: nil, // set only for walletTypeRPC below
175170
}
176171

177172
switch cfg.Type {
178173
case walletTypeRPC:
179174
var exw *btc.ExchangeWalletFullNode
180-
// override PrivKeyFunc - we need our own Firo dumpprivkey fn
181175
cloneCFG.PrivKeyFunc = func(addr string) (*btcec.PrivateKey, error) {
182176
return privKeyForAddress(exw, addr)
183177
}
178+
cloneCFG.BlockDeserializer = func(blk []byte) (*wire.MsgBlock, error) {
179+
return deserializeBlock(params, blk)
180+
}
184181
var err error
185182
exw, err = btc.BTCCloneWallet(cloneCFG)
186183
return exw, err

0 commit comments

Comments
 (0)