-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
client/asset/ltc: work with and require v0.21.2
This updates the client's supported Litecoin Core version to v0.21.2 This is the modern Litecoin Core, with the MWEB patch set. In addition to bumping the version, this means: - getbalances is supported now - sendrawtransaction has the new syntax with maxfeerate not allowHighFee This also adds the missing switches on wallet Type to both ltc and bch. To support MWEB and MW txns, this defines custom block and transaction deserializers. This is based on the LIPS: https://github.com/litecoin-project/lips/blob/master/lip-0002.mediawiki https://github.com/litecoin-project/lips/blob/master/lip-0003.mediawiki On LTC regtest/regnet, the mweb soft fork is not active at genesis. It is necessary to mine to block 431, send to a mweb address in a "peg-in" transaction, and then mine the block so that the pegin and the required hogex may be created when mweb activates at 432. As such this also regenerates the litecoin harness chain tarball. As of Bitcoin Core 0.21, the default "" wallet is not automatically created. https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.21.0.md#automatic-wallet-creation-removed This adds a CREATE_DEFAULT_WALLET variable to the harness script to explicitly create this wallet if needed. The command also creates it encrypted to start.
- Loading branch information
Showing
28 changed files
with
1,154 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// This code is available on the terms of the project LICENSE.md file, | ||
// also available online at https://blueoakcouncil.org | ||
|
||
package ltc | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/btcsuite/btcd/wire" | ||
) | ||
|
||
const ( | ||
// mwebVer is the bit of the block header's version that indicates the | ||
// presence of a MWEB. | ||
mwebVer = 0x20000000 // 1 << 29 | ||
) | ||
|
||
func parseMWEB(blk io.Reader) error { | ||
dec := newDecoder(blk) | ||
// src/mweb/mweb_models.h - struct Block | ||
// "A convenience wrapper around a possibly-null extension block."" | ||
// OptionalPtr around a mw::Block. Read the option byte: | ||
hasMWEB, err := dec.readByte() | ||
if err != nil { | ||
return fmt.Errorf("failed to check MWEB option byte: %w", err) | ||
} | ||
if hasMWEB == 0 { | ||
return nil | ||
} | ||
|
||
// src/libmw/include/mw/models/block/Block.h - class Block | ||
// (1) Header and (2) TxBody | ||
|
||
// src/libmw/include/mw/models/block/Header.h - class Header | ||
// height | ||
if _, err = dec.readVLQ(); err != nil { | ||
return fmt.Errorf("failed to decode MWEB height: %w", err) | ||
} | ||
|
||
// 3x Hash + 2x BlindingFactor | ||
if err = dec.discardBytes(32*3 + 32*2); err != nil { | ||
return fmt.Errorf("failed to decode MWEB junk: %w", err) | ||
} | ||
|
||
// Number of TXOs: outputMMRSize | ||
if _, err = dec.readVLQ(); err != nil { | ||
return fmt.Errorf("failed to decode TXO count: %w", err) | ||
} | ||
|
||
// Number of kernels: kernelMMRSize | ||
if _, err = dec.readVLQ(); err != nil { | ||
return fmt.Errorf("failed to decode kernel count: %w", err) | ||
} | ||
|
||
// TxBody | ||
_, err = dec.readMWTXBody() | ||
if err != nil { | ||
return fmt.Errorf("failed to decode MWEB tx: %w", err) | ||
} | ||
// if len(kern0) > 0 { | ||
// mwebTxID := chainhash.Hash(blake3.Sum256(kern0)) | ||
// fmt.Println(mwebTxID.String()) | ||
// } | ||
|
||
return nil | ||
} | ||
|
||
// DeserializeBlock decodes the bytes of a serialized Litecoin block. This | ||
// function exists because MWEB changes both the block and transaction | ||
// serializations. Blocks may have a MW "extension block" for "peg-out" | ||
// transactions, and this EB is after all the transactions in the regular LTC | ||
// block. After the canonical transactions in the regular block, there may be | ||
// zero or more "peg-in" transactions followed by one integration transaction | ||
// (also known as a HogEx transaction), all still in the regular LTC block. The | ||
// peg-in txns decode correctly, but the integration tx is a special transaction | ||
// with the witness tx flag with bit 3 set (8), which prevents correct | ||
// wire.MsgTx deserialization. | ||
// Refs: | ||
// https://github.com/litecoin-project/lips/blob/master/lip-0002.mediawiki#PegOut_Transactions | ||
// https://github.com/litecoin-project/lips/blob/master/lip-0003.mediawiki#Specification | ||
// https://github.com/litecoin-project/litecoin/commit/9d1f530a5fa6d16871fdcc3b506be42b593d3ce4 | ||
// https://github.com/litecoin-project/litecoin/commit/8c82032f45e644f413ec5c91e121a31c993aa831 | ||
// (src/libmw/include/mw/models/tx/Transaction.h for the `mweb_tx` field of the | ||
// `CTransaction` in the "primitives" commit). | ||
func DeserializeBlock(blk io.Reader) (*wire.MsgBlock, error) { | ||
// Block header | ||
hdr := &wire.BlockHeader{} | ||
err := hdr.Deserialize(blk) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to deserialize block header: %w", err) | ||
} | ||
|
||
// This block's transactions | ||
txnCount, err := wire.ReadVarInt(blk, 0) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse transaction count: %w", err) | ||
} | ||
|
||
// We can only decode the canonical txns, not the mw peg-in txs in the EB. | ||
var hasHogEx bool | ||
txns := make([]*wire.MsgTx, 0, int(txnCount)) | ||
for i := 0; i < cap(txns); i++ { | ||
msgTx, err := DeserializeTx(blk) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to deserialize transaction %d of %d in block %v: %w", | ||
i+1, txnCount, hdr.BlockHash(), err) | ||
} | ||
txns = append(txns, msgTx.MsgTx) // txns = append(txns, msgTx) | ||
hasHogEx = msgTx.IsHogEx // hogex is the last txn | ||
} | ||
|
||
// The mwebVer mask indicates it may contain a MWEB after a HogEx. | ||
// src/primitives/block.h: SERIALIZE_NO_MWEB | ||
if hdr.Version&mwebVer != 0 && hasHogEx { | ||
if err = parseMWEB(blk); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
return &wire.MsgBlock{ | ||
Header: *hdr, | ||
Transactions: txns, | ||
}, nil | ||
} | ||
|
||
// DeserializeBlockBytes wraps DeserializeBlock using bytes.NewReader for | ||
// convenience. | ||
func DeserializeBlockBytes(blk []byte) (*wire.MsgBlock, error) { | ||
return DeserializeBlock(bytes.NewReader(blk)) | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// This code is available on the terms of the project LICENSE.md file, | ||
// also available online at https://blueoakcouncil.org | ||
|
||
//go:build ltclive | ||
|
||
package ltc | ||
|
||
import ( | ||
"context" | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"os/user" | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"decred.org/dcrdex/dex/config" | ||
|
||
"github.com/btcsuite/btcd/btcjson" | ||
"github.com/decred/dcrd/chaincfg/chainhash" // just for the array type, not its methods | ||
"github.com/decred/dcrd/rpcclient/v7" | ||
) | ||
|
||
// TestOnlineDeserializeBlock attempts to deserialize every testnet4 LTC block | ||
// from 1000 blocks prior to mweb to the chain tip. | ||
func TestOnlineDeserializeBlock(t *testing.T) { | ||
cfg := struct { | ||
RPCUser string `ini:"rpcuser"` | ||
RPCPass string `ini:"rpcpassword"` | ||
}{} | ||
usr, _ := user.Current() | ||
if err := config.ParseInto(filepath.Join(usr.HomeDir, ".litecoin", "litecoin.conf"), &cfg); err != nil { | ||
t.Fatalf("config.ParseInto error: %v", err) | ||
} | ||
client, err := rpcclient.New(&rpcclient.ConnConfig{ | ||
HTTPPostMode: true, | ||
DisableTLS: true, | ||
Host: "127.0.0.1:19332", // testnet4, mainnet is 9332 | ||
User: cfg.RPCUser, | ||
Pass: cfg.RPCPass, | ||
}, nil) | ||
if err != nil { | ||
t.Fatalf("error creating RPC client: %v", err) | ||
} | ||
|
||
msg, err := client.RawRequest(context.Background(), "getblockchaininfo", nil) | ||
if err != nil { | ||
t.Fatalf("getblockchaininfo: %v", err) | ||
} | ||
gbci := &struct { | ||
Chain string `json:"chain"` | ||
}{} | ||
err = json.Unmarshal(msg, &gbci) | ||
if err != nil { | ||
t.Fatalf("Unmarshal: %v", err) | ||
} | ||
isTestNet := gbci.Chain == "test" | ||
t.Log("Network:", gbci.Chain) | ||
|
||
makeParams := func(args ...interface{}) []json.RawMessage { | ||
params := make([]json.RawMessage, 0, len(args)) | ||
for i := range args { | ||
p, err := json.Marshal(args[i]) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
params = append(params, p) | ||
} | ||
return params | ||
} | ||
|
||
getBlkStr := func(hash *chainhash.Hash) string { | ||
params := makeParams(hash.String(), 0) // not verbose | ||
msg, err := client.RawRequest(context.Background(), "getblock", params) | ||
if err != nil { | ||
t.Fatalf("RawRequest: %v", err) | ||
} | ||
var blkStr string | ||
err = json.Unmarshal(msg, &blkStr) | ||
if err != nil { | ||
t.Fatalf("Unmarshal: %v", err) | ||
} | ||
return blkStr | ||
} | ||
|
||
getBlkVerbose := func(hash *chainhash.Hash) *btcjson.GetBlockVerboseResult { | ||
params := makeParams(hash.String(), 1) // verbose | ||
msg, err := client.RawRequest(context.Background(), "getblock", params) | ||
if err != nil { | ||
t.Fatalf("RawRequest: %v", err) | ||
} | ||
var res btcjson.GetBlockVerboseResult | ||
err = json.Unmarshal(msg, &res) | ||
if err != nil { | ||
t.Fatalf("Unmarshal: %v", err) | ||
} | ||
return &res | ||
} | ||
|
||
// start 1000 blocks prior to mweb testnet blocks | ||
// 2215586 for mainnet, 2214584 for testnet4 | ||
var startBlk, numBlocks int64 = 2214584, 2000 | ||
if isTestNet { | ||
numBlocks = 12000 // testnet blocks are empty and fast | ||
} | ||
for iBlk := startBlk; iBlk <= startBlk+numBlocks; iBlk++ { | ||
hash, err := client.GetBlockHash(context.Background(), iBlk) | ||
if err != nil { | ||
if strings.Contains(err.Error(), "height out of range") { | ||
break | ||
} | ||
t.Fatal(err) // hint: 401 means unauthorized (check user/pass above) | ||
} | ||
blkStr := getBlkStr(hash) | ||
blk, err := DeserializeBlock(hex.NewDecoder(strings.NewReader(blkStr))) | ||
if err != nil { | ||
t.Fatalf("Unmarshal (%d): %v", iBlk, err) | ||
} | ||
if iBlk%500 == 0 { | ||
fmt.Println(iBlk) | ||
} | ||
gbv := getBlkVerbose(hash) | ||
if len(blk.Transactions) != len(gbv.Tx) { | ||
t.Fatalf("block %v has %d but decoded %d txns", hash, len(gbv.Tx), len(blk.Transactions)) | ||
} | ||
for i, tx := range blk.Transactions { | ||
txid := tx.TxHash().String() | ||
if txid != gbv.Tx[i] { | ||
t.Errorf("got txid %v, wanted %v", txid, gbv.Tx[i]) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.