increase the 252 per-block transaction limit (#273)
This commit is contained in:
parent
e665ebe951
commit
faca1ecbef
|
@ -7,3 +7,4 @@ test-log
|
|||
lwd-api.html
|
||||
*.orig
|
||||
__debug_bin
|
||||
.vscode
|
||||
|
|
|
@ -46,10 +46,13 @@ func TestCache(t *testing.T) {
|
|||
for _, test := range compactTests {
|
||||
blockData, _ := hex.DecodeString(test.Full)
|
||||
block := parser.NewBlock()
|
||||
_, err = block.ParseFromSlice(blockData)
|
||||
blockData, err = block.ParseFromSlice(blockData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(blockData) > 0 {
|
||||
t.Error("Extra data remaining")
|
||||
}
|
||||
compacts = append(compacts, block.ToCompact())
|
||||
}
|
||||
|
||||
|
|
|
@ -178,11 +178,32 @@ func DarksideApplyStaged(height int) error {
|
|||
return errors.New("transaction height too high")
|
||||
}
|
||||
block := state.activeBlocks[tx.height-state.startHeight]
|
||||
if block[1487] == 253 {
|
||||
return errors.New("too many transactions in a block (max 253)")
|
||||
// The next one or 3 bytes encode the number of transactions to follow,
|
||||
// little endian.
|
||||
nTxFirstByte := block[1487]
|
||||
switch {
|
||||
case nTxFirstByte < 252:
|
||||
block[1487]++
|
||||
case nTxFirstByte == 252:
|
||||
// incrementing to 253, requires "253" followed by 2-byte length,
|
||||
// extend the block by two bytes, shift existing transaction bytes
|
||||
block = append(block, 0, 0)
|
||||
copy(block[1490:], block[1488:len(block)-2])
|
||||
block[1487] = 253
|
||||
block[1488] = 253
|
||||
block[1489] = 0
|
||||
case nTxFirstByte == 253:
|
||||
block[1488]++
|
||||
if block[1488] == 0 {
|
||||
// wrapped around
|
||||
block[1489]++
|
||||
}
|
||||
default:
|
||||
// no need to worry about more than 64k transactions
|
||||
Log.Fatal("unexpected compact transaction count ", nTxFirstByte,
|
||||
", can't support more than 64k transactions in a block")
|
||||
}
|
||||
block[1487]++ // one more transaction
|
||||
block[68]++ // hack HashFinalSaplingRoot to mod the block hash
|
||||
block[68]++ // hack HashFinalSaplingRoot to mod the block hash
|
||||
block = append(block, tx.bytes...)
|
||||
state.activeBlocks[tx.height-state.startHeight] = block
|
||||
}
|
||||
|
@ -244,7 +265,7 @@ func DarksideStageBlockStream(blockHex string) error {
|
|||
if !state.resetted {
|
||||
return errors.New("please call Reset first")
|
||||
}
|
||||
Log.Info("StageBlocks()")
|
||||
Log.Info("StageBlocksStream()")
|
||||
blockBytes, err := hex.DecodeString(blockHex)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -445,7 +466,7 @@ func DarksideStageTransactionsURL(height int, url string) error {
|
|||
if !state.resetted {
|
||||
return errors.New("please call Reset first")
|
||||
}
|
||||
Log.Info("StageTransactionsURL(height=", height, "url=", url, ")")
|
||||
Log.Info("StageTransactionsURL(height=", height, " url=", url, ")")
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -738,8 +738,8 @@ from the given URL. Blocks are one per line, hex-encoded (not JSON).</p></td>
|
|||
<td><p>StageBlocksCreate is like the previous two, except it creates 'count'
|
||||
empty blocks at consecutive heights starting at height 'height'. The
|
||||
'nonce' is part of the header, so it contributes to the block hash; this
|
||||
lets you create two fake blocks with the same transactions (or no
|
||||
transactions) and same height, with two different hashes.</p></td>
|
||||
lets you create identical blocks (same transactions and height), but with
|
||||
different hashes.</p></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
@ -757,10 +757,9 @@ by the mock zcashd).</p></td>
|
|||
<td>StageTransactions</td>
|
||||
<td><a href="#cash.z.wallet.sdk.rpc.DarksideTransactionsURL">DarksideTransactionsURL</a></td>
|
||||
<td><a href="#cash.z.wallet.sdk.rpc.Empty">Empty</a></td>
|
||||
<td><p>StageTransactions is the same except the transactions are fetched
|
||||
from the given url. They are all staged into the block at the given
|
||||
height. Staging transactions at multiple different heights requires
|
||||
multiple calls.</p></td>
|
||||
<td><p>StageTransactions is the same except the transactions are fetched from
|
||||
the given url. They are all staged into the block at the given height.
|
||||
Staging transactions to different heights requires multiple calls.</p></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
@ -770,12 +769,14 @@ multiple calls.</p></td>
|
|||
<td><p>ApplyStaged iterates the list of blocks that were staged by the
|
||||
StageBlocks*() gRPCs, in the order they were staged, and "merges" each
|
||||
into the active, working blocks list that the mock zcashd is presenting
|
||||
to lightwalletd. The resulting working block list can't have gaps; if the
|
||||
working block range is 1000-1006, and the staged block range is 1003-1004,
|
||||
the resulting range is 1000-1004, with 1000-1002 unchanged, blocks
|
||||
1003-1004 from the new range, and 1005-1006 dropped. After merging all
|
||||
blocks, ApplyStaged() appends staged transactions (in the order received)
|
||||
into each one's corresponding block. The staging area is then cleared.
|
||||
to lightwalletd. Even as each block is applied, the active list can't
|
||||
have gaps; if the active block range is 1000-1006, and the staged block
|
||||
range is 1003-1004, the resulting range is 1000-1004, with 1000-1002
|
||||
unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped.
|
||||
|
||||
After merging all blocks, ApplyStaged() appends staged transactions (in
|
||||
the order received) into each one's corresponding (by height) block
|
||||
The staging area is then cleared.
|
||||
|
||||
The argument specifies the latest block height that mock zcashd reports
|
||||
(i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) 2019-2020 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
// Package parser deserializes blocks from zcashd.
|
||||
package parser
|
||||
|
||||
import (
|
||||
|
@ -11,24 +13,30 @@ import (
|
|||
"github.com/zcash/lightwalletd/walletrpc"
|
||||
)
|
||||
|
||||
// Block represents a full block (not a compact block).
|
||||
type Block struct {
|
||||
hdr *BlockHeader
|
||||
vtx []*Transaction
|
||||
height int
|
||||
}
|
||||
|
||||
// NewBlock constructs a block instance.
|
||||
func NewBlock() *Block {
|
||||
return &Block{height: -1}
|
||||
}
|
||||
|
||||
// GetVersion returns a block's version number (current 4)
|
||||
func (b *Block) GetVersion() int {
|
||||
return int(b.hdr.Version)
|
||||
}
|
||||
|
||||
// GetTxCount returns the number of transactions in the block,
|
||||
// including the coinbase transaction (minimum 1).
|
||||
func (b *Block) GetTxCount() int {
|
||||
return len(b.vtx)
|
||||
}
|
||||
|
||||
// Transactions returns the list of the block's transactions.
|
||||
func (b *Block) Transactions() []*Transaction {
|
||||
// TODO: these should NOT be mutable
|
||||
return b.vtx
|
||||
|
@ -46,10 +54,12 @@ func (b *Block) GetEncodableHash() []byte {
|
|||
return b.hdr.GetEncodableHash()
|
||||
}
|
||||
|
||||
// GetDisplayPrevHash returns the block's previous hash in big-endian format.
|
||||
func (b *Block) GetDisplayPrevHash() []byte {
|
||||
return b.hdr.GetDisplayPrevHash()
|
||||
}
|
||||
|
||||
// HasSaplingTransactions indicates if the block contains any Sapling tx.
|
||||
func (b *Block) HasSaplingTransactions() bool {
|
||||
for _, tx := range b.vtx {
|
||||
if tx.HasSaplingElements() {
|
||||
|
@ -62,7 +72,7 @@ func (b *Block) HasSaplingTransactions() bool {
|
|||
// see https://github.com/zcash/lightwalletd/issues/17#issuecomment-467110828
|
||||
const genesisTargetDifficulty = 520617983
|
||||
|
||||
// GetHeight() extracts the block height from the coinbase transaction. See
|
||||
// GetHeight extracts the block height from the coinbase transaction. See
|
||||
// BIP34. Returns block height on success, or -1 on error.
|
||||
func (b *Block) GetHeight() int {
|
||||
if b.height != -1 {
|
||||
|
@ -90,10 +100,12 @@ func (b *Block) GetHeight() int {
|
|||
return int(blockHeight)
|
||||
}
|
||||
|
||||
// GetPrevHash returns the hash of the block's previous block (little-endian).
|
||||
func (b *Block) GetPrevHash() []byte {
|
||||
return b.hdr.HashPrevBlock
|
||||
}
|
||||
|
||||
// ToCompact returns the compact representation of the full block.
|
||||
func (b *Block) ToCompact() *walletrpc.CompactBlock {
|
||||
compactBlock := &walletrpc.CompactBlock{
|
||||
//TODO ProtoVersion: 1,
|
||||
|
@ -114,6 +126,9 @@ func (b *Block) ToCompact() *walletrpc.CompactBlock {
|
|||
return compactBlock
|
||||
}
|
||||
|
||||
// ParseFromSlice deserializes a block from the given data stream
|
||||
// and returns a slice to the remaining data. The caller should verify
|
||||
// there is no remaining data if none is expected.
|
||||
func (b *Block) ParseFromSlice(data []byte) (rest []byte, err error) {
|
||||
hdr := NewBlockHeader()
|
||||
data, err = hdr.ParseFromSlice(data)
|
||||
|
@ -129,7 +144,8 @@ func (b *Block) ParseFromSlice(data []byte) (rest []byte, err error) {
|
|||
data = []byte(s)
|
||||
|
||||
vtx := make([]*Transaction, 0, txCount)
|
||||
for i := 0; len(data) > 0; i++ {
|
||||
var i int
|
||||
for i = 0; i < txCount && len(data) > 0; i++ {
|
||||
tx := NewTransaction()
|
||||
data, err = tx.ParseFromSlice(data)
|
||||
if err != nil {
|
||||
|
@ -137,9 +153,10 @@ func (b *Block) ParseFromSlice(data []byte) (rest []byte, err error) {
|
|||
}
|
||||
vtx = append(vtx, tx)
|
||||
}
|
||||
|
||||
if i < txCount {
|
||||
return nil, errors.New("parsing block transactions: not enough data")
|
||||
}
|
||||
b.hdr = hdr
|
||||
b.vtx = vtx
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) 2019-2020 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
// Package parser deserializes the block header from zcashd.
|
||||
package parser
|
||||
|
||||
import (
|
||||
|
@ -18,7 +20,8 @@ const (
|
|||
equihashSizeMainnet = 1344 // size of a mainnet / testnet Equihash solution in bytes
|
||||
)
|
||||
|
||||
// A block header as defined in version 2018.0-beta-29 of the Zcash Protocol Spec.
|
||||
// RawBlockHeader implements the block header as defined in version
|
||||
// 2018.0-beta-29 of the Zcash Protocol Spec.
|
||||
type RawBlockHeader struct {
|
||||
// The block version number indicates which set of block validation rules
|
||||
// to follow. The current and only defined block version number for Zcash
|
||||
|
@ -58,6 +61,7 @@ type RawBlockHeader struct {
|
|||
Solution []byte
|
||||
}
|
||||
|
||||
// BlockHeader extends RawBlockHeader by adding a cache for the block hash.
|
||||
type BlockHeader struct {
|
||||
*RawBlockHeader
|
||||
cachedHash []byte
|
||||
|
@ -93,17 +97,18 @@ func WriteCompactLengthPrefixedLen(buf *bytes.Buffer, length int) {
|
|||
}
|
||||
}
|
||||
|
||||
func WriteCompactLengthPrefixed(buf *bytes.Buffer, val []byte) {
|
||||
func writeCompactLengthPrefixed(buf *bytes.Buffer, val []byte) {
|
||||
WriteCompactLengthPrefixedLen(buf, len(val))
|
||||
binary.Write(buf, binary.LittleEndian, val)
|
||||
}
|
||||
|
||||
func (hdr *RawBlockHeader) GetSize() int {
|
||||
func (hdr *RawBlockHeader) getSize() int {
|
||||
return serBlockHeaderMinusEquihashSize + CompactLengthPrefixedLen(len(hdr.Solution))
|
||||
}
|
||||
|
||||
// MarshalBinary returns the block header in serialized form
|
||||
func (hdr *RawBlockHeader) MarshalBinary() ([]byte, error) {
|
||||
headerSize := hdr.GetSize()
|
||||
headerSize := hdr.getSize()
|
||||
backing := make([]byte, 0, headerSize)
|
||||
buf := bytes.NewBuffer(backing)
|
||||
binary.Write(buf, binary.LittleEndian, hdr.Version)
|
||||
|
@ -113,7 +118,7 @@ func (hdr *RawBlockHeader) MarshalBinary() ([]byte, error) {
|
|||
binary.Write(buf, binary.LittleEndian, hdr.Time)
|
||||
binary.Write(buf, binary.LittleEndian, hdr.NBitsBytes)
|
||||
binary.Write(buf, binary.LittleEndian, hdr.Nonce)
|
||||
WriteCompactLengthPrefixed(buf, hdr.Solution)
|
||||
writeCompactLengthPrefixed(buf, hdr.Solution)
|
||||
return backing[:headerSize], nil
|
||||
}
|
||||
|
||||
|
@ -229,6 +234,7 @@ func (hdr *BlockHeader) GetEncodableHash() []byte {
|
|||
return digest[:]
|
||||
}
|
||||
|
||||
// GetDisplayPrevHash returns the block hash in
|
||||
func (hdr *BlockHeader) GetDisplayPrevHash() []byte {
|
||||
rhash := make([]byte, len(hdr.HashPrevBlock))
|
||||
copy(rhash, hdr.HashPrevBlock)
|
||||
|
|
|
@ -249,7 +249,7 @@ func TestWriteCompactLengthPrefixedLen(t *testing.T) {
|
|||
func TestWriteCompactLengthPrefixed(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
val := []byte{22, 33, 44}
|
||||
WriteCompactLengthPrefixed(&b, val)
|
||||
writeCompactLengthPrefixed(&b, val)
|
||||
r := make([]byte, 4)
|
||||
b.Read(r)
|
||||
expected := []byte{3, 22, 33, 44}
|
||||
|
|
|
@ -19,16 +19,21 @@ import (
|
|||
)
|
||||
|
||||
func TestBlockParser(t *testing.T) {
|
||||
// These (valid on testnet) correspond to the transactions in testdata/blocks
|
||||
var txhashes = []string{
|
||||
"81096ff101a4f01d25ffd34a446bee4368bd46c233a59ac0faf101e1861c6b22",
|
||||
"921dc41bef3a0d887c615abac60a29979efc8b4bbd3d887caeb6bb93501bde8e",
|
||||
"d8e4c336ffa69dacaa4e0b4eaf8e3ae46897f1930a573c10b53837a03318c980",
|
||||
"4d5ccbfc6984680c481ff5ce145b8a93d59dfea90c150dfa45c938ab076ee5b2",
|
||||
"df2b03619d441ce3d347e9278d87618e975079d0e235dfb3b3d8271510f707aa",
|
||||
"8d2593edfc328fa637b4ac91c7d569ee922bb9a6fda7cea230e92deb3ae4b634",
|
||||
// These (valid on testnet) correspond to the transactions in testdata/blocks;
|
||||
// for each block, the hashes for the tx within that block.
|
||||
var txhashes = [][]string{
|
||||
{
|
||||
"81096ff101a4f01d25ffd34a446bee4368bd46c233a59ac0faf101e1861c6b22",
|
||||
}, {
|
||||
"921dc41bef3a0d887c615abac60a29979efc8b4bbd3d887caeb6bb93501bde8e",
|
||||
}, {
|
||||
"d8e4c336ffa69dacaa4e0b4eaf8e3ae46897f1930a573c10b53837a03318c980",
|
||||
"4d5ccbfc6984680c481ff5ce145b8a93d59dfea90c150dfa45c938ab076ee5b2",
|
||||
}, {
|
||||
"df2b03619d441ce3d347e9278d87618e975079d0e235dfb3b3d8271510f707aa",
|
||||
"8d2593edfc328fa637b4ac91c7d569ee922bb9a6fda7cea230e92deb3ae4b634",
|
||||
},
|
||||
}
|
||||
txindex := 0
|
||||
testBlocks, err := os.Open("../testdata/blocks")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -36,7 +41,7 @@ func TestBlockParser(t *testing.T) {
|
|||
defer testBlocks.Close()
|
||||
|
||||
scan := bufio.NewScanner(testBlocks)
|
||||
for i := 0; scan.Scan(); i++ {
|
||||
for blockindex := 0; scan.Scan(); blockindex++ {
|
||||
blockDataHex := scan.Text()
|
||||
blockData, err := hex.DecodeString(blockDataHex)
|
||||
if err != nil {
|
||||
|
@ -44,43 +49,92 @@ func TestBlockParser(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
|
||||
block := NewBlock()
|
||||
blockData, err = block.ParseFromSlice(blockData)
|
||||
if err != nil {
|
||||
t.Error(errors.Wrap(err, fmt.Sprintf("parsing block %d", i)))
|
||||
continue
|
||||
// This is just a sanity check of the test:
|
||||
if int(blockData[1487]) != len(txhashes[blockindex]) {
|
||||
t.Error("wrong number of transactions, test broken?")
|
||||
}
|
||||
|
||||
// Some basic sanity checks
|
||||
if block.hdr.Version != 4 {
|
||||
t.Error("Read wrong version in a test block.")
|
||||
break
|
||||
}
|
||||
if block.GetVersion() != 4 {
|
||||
t.Error("Read wrong version in a test block.")
|
||||
break
|
||||
}
|
||||
if block.GetTxCount() < 1 {
|
||||
t.Error("No transactions in block")
|
||||
break
|
||||
}
|
||||
if len(block.Transactions()) != block.GetTxCount() {
|
||||
t.Error("Number of transactions mismatch")
|
||||
break
|
||||
}
|
||||
if block.HasSaplingTransactions() {
|
||||
t.Error("Unexpected Saping tx")
|
||||
break
|
||||
}
|
||||
for _, tx := range block.Transactions() {
|
||||
if tx.HasSaplingElements() {
|
||||
// Make a copy of just the transactions alone, which,
|
||||
// for these blocks, start just beyond the header and
|
||||
// the one-byte nTx value, which is offset 1488.
|
||||
transactions := make([]byte, len(blockData[1488:]))
|
||||
copy(transactions, blockData[1488:])
|
||||
|
||||
// Each iteration of this loop appends the block's original
|
||||
// transactions, so we build an ever-larger block. The loop
|
||||
// limit is arbitrary, but make sure we get into double-digit
|
||||
// transaction counts (compact integer).
|
||||
for i := 0; i < 264; i++ {
|
||||
b := blockData
|
||||
block := NewBlock()
|
||||
b, err = block.ParseFromSlice(b)
|
||||
if err != nil {
|
||||
t.Error(errors.Wrap(err, fmt.Sprintf("parsing block %d", i)))
|
||||
continue
|
||||
}
|
||||
if len(b) > 0 {
|
||||
t.Error("Extra data remaining")
|
||||
}
|
||||
|
||||
// Some basic sanity checks
|
||||
if block.hdr.Version != 4 {
|
||||
t.Error("Read wrong version in a test block.")
|
||||
break
|
||||
}
|
||||
if block.GetVersion() != 4 {
|
||||
t.Error("Read wrong version in a test block.")
|
||||
break
|
||||
}
|
||||
if block.GetTxCount() < 1 {
|
||||
t.Error("No transactions in block")
|
||||
break
|
||||
}
|
||||
if len(block.Transactions()) != block.GetTxCount() {
|
||||
t.Error("Number of transactions mismatch")
|
||||
break
|
||||
}
|
||||
if block.GetTxCount() != len(txhashes[blockindex])*(i+1) {
|
||||
t.Error("Unexpected number of transactions")
|
||||
}
|
||||
if block.HasSaplingTransactions() {
|
||||
t.Error("Unexpected Saping tx")
|
||||
break
|
||||
}
|
||||
if hex.EncodeToString(tx.GetDisplayHash()) != txhashes[txindex] {
|
||||
t.Error("incorrect tx hash")
|
||||
for txindex, tx := range block.Transactions() {
|
||||
if tx.HasSaplingElements() {
|
||||
t.Error("Unexpected Saping tx")
|
||||
break
|
||||
}
|
||||
expectedHash := txhashes[blockindex][txindex%len(txhashes[blockindex])]
|
||||
if hex.EncodeToString(tx.GetDisplayHash()) != expectedHash {
|
||||
t.Error("incorrect tx hash")
|
||||
}
|
||||
}
|
||||
txindex++
|
||||
// Keep appending the original transactions, which is unrealistic
|
||||
// because the coinbase is being replicated, but it works; first do
|
||||
// some surgery to the transaction count (see DarksideApplyStaged()).
|
||||
for j := 0; j < len(txhashes[blockindex]); j++ {
|
||||
nTxFirstByte := blockData[1487]
|
||||
switch {
|
||||
case nTxFirstByte < 252:
|
||||
blockData[1487]++
|
||||
case nTxFirstByte == 252:
|
||||
// incrementing to 253, requires "253" followed by 2-byte length,
|
||||
// extend the block by two bytes, shift existing transaction bytes
|
||||
blockData = append(blockData, 0, 0)
|
||||
copy(blockData[1490:], blockData[1488:len(blockData)-2])
|
||||
blockData[1487] = 253
|
||||
blockData[1488] = 253
|
||||
blockData[1489] = 0
|
||||
case nTxFirstByte == 253:
|
||||
blockData[1488]++
|
||||
if blockData[1488] == 0 {
|
||||
// wrapped around
|
||||
blockData[1489]++
|
||||
}
|
||||
}
|
||||
}
|
||||
blockData = append(blockData, transactions...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,6 +196,9 @@ func TestGenesisBlockParser(t *testing.T) {
|
|||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
if len(blockData) > 0 {
|
||||
t.Error("Extra data remaining")
|
||||
}
|
||||
|
||||
// Some basic sanity checks
|
||||
if block.hdr.Version != 4 {
|
||||
|
@ -183,6 +240,9 @@ func TestCompactBlocks(t *testing.T) {
|
|||
t.Error(errors.Wrap(err, fmt.Sprintf("parsing testnet block %d", test.BlockHeight)))
|
||||
continue
|
||||
}
|
||||
if len(blockData) > 0 {
|
||||
t.Error("Extra data remaining")
|
||||
}
|
||||
if block.GetHeight() != test.BlockHeight {
|
||||
t.Errorf("incorrect block height in testnet block %d", test.BlockHeight)
|
||||
continue
|
||||
|
|
|
@ -185,6 +185,8 @@ func TestString_ReadBytes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// compact sizes are little-endian (least significant byte first, lower memory addr),
|
||||
// see https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
|
||||
var readCompactSizeTests = []struct {
|
||||
s String
|
||||
ok bool
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) 2019-2020 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
// Package parser deserializes (full) transactions (zcashd).
|
||||
package parser
|
||||
|
||||
import (
|
||||
|
@ -14,7 +16,7 @@ import (
|
|||
type rawTransaction struct {
|
||||
fOverwintered bool
|
||||
version uint32
|
||||
nVersionGroupId uint32
|
||||
nVersionGroupID uint32
|
||||
transparentInputs []*txIn
|
||||
transparentOutputs []*txOut
|
||||
nLockTime uint32
|
||||
|
@ -266,16 +268,17 @@ func (p *joinSplit) ParseFromSlice(data []byte) ([]byte, error) {
|
|||
return []byte(s), nil
|
||||
}
|
||||
|
||||
// Transaction encodes a full (zcashd) transaction.
|
||||
type Transaction struct {
|
||||
*rawTransaction
|
||||
rawBytes []byte
|
||||
txId []byte
|
||||
txID []byte
|
||||
}
|
||||
|
||||
// GetDisplayHash returns the transaction hash in big-endian display order.
|
||||
func (tx *Transaction) GetDisplayHash() []byte {
|
||||
if tx.txId != nil {
|
||||
return tx.txId
|
||||
if tx.txID != nil {
|
||||
return tx.txID
|
||||
}
|
||||
|
||||
// SHA256d
|
||||
|
@ -288,8 +291,8 @@ func (tx *Transaction) GetDisplayHash() []byte {
|
|||
digest[i], digest[j] = digest[j], digest[i]
|
||||
}
|
||||
|
||||
tx.txId = digest[:]
|
||||
return tx.txId
|
||||
tx.txID = digest[:]
|
||||
return tx.txID
|
||||
}
|
||||
|
||||
// GetEncodableHash returns the transaction hash in little-endian wire format order.
|
||||
|
@ -299,14 +302,18 @@ func (tx *Transaction) GetEncodableHash() []byte {
|
|||
return digest[:]
|
||||
}
|
||||
|
||||
// Bytes returns a full transaction's raw bytes.
|
||||
func (tx *Transaction) Bytes() []byte {
|
||||
return tx.rawBytes
|
||||
}
|
||||
|
||||
// HasSaplingElements indicates whether a transaction has
|
||||
// at least one shielded input or output.
|
||||
func (tx *Transaction) HasSaplingElements() bool {
|
||||
return tx.version >= 4 && (len(tx.shieldedSpends)+len(tx.shieldedOutputs)) > 0
|
||||
}
|
||||
|
||||
// ToCompact converts the given (full) transaction to compact format.
|
||||
func (tx *Transaction) ToCompact(index int) *walletrpc.CompactTx {
|
||||
ctx := &walletrpc.CompactTx{
|
||||
Index: uint64(index), // index is contextual
|
||||
|
@ -324,6 +331,7 @@ func (tx *Transaction) ToCompact(index int) *walletrpc.CompactTx {
|
|||
return ctx
|
||||
}
|
||||
|
||||
// ParseFromSlice deserializes a single transaction from the given data.
|
||||
func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) {
|
||||
s := bytestring.String(data)
|
||||
|
||||
|
@ -339,7 +347,7 @@ func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) {
|
|||
tx.version = header & 0x7FFFFFFF
|
||||
|
||||
if tx.version >= 3 {
|
||||
if !s.ReadUint32(&tx.nVersionGroupId) {
|
||||
if !s.ReadUint32(&tx.nVersionGroupID) {
|
||||
return nil, errors.New("could not read nVersionGroupId")
|
||||
}
|
||||
}
|
||||
|
@ -472,6 +480,7 @@ func (tx *Transaction) ParseFromSlice(data []byte) ([]byte, error) {
|
|||
return []byte(s), nil
|
||||
}
|
||||
|
||||
// NewTransaction is the constructor for a full transaction.
|
||||
func NewTransaction() *Transaction {
|
||||
return &Transaction{
|
||||
rawTransaction: new(rawTransaction),
|
||||
|
|
|
@ -51,7 +51,7 @@ type outputTestVector struct {
|
|||
|
||||
type txTestVector struct {
|
||||
// Sprout and Sapling
|
||||
txid, header, nVersionGroupId, nLockTime, nExpiryHeight string
|
||||
txid, header, nVersionGroupID, nLockTime, nExpiryHeight string
|
||||
vin, vout [][]string
|
||||
vJoinSplits []joinSplitTestVector
|
||||
joinSplitPubKey, joinSplitSig string
|
||||
|
@ -69,7 +69,7 @@ var zip143tests = []txTestVector{
|
|||
// Test vector 1
|
||||
txid: "f0b22277ac851b5f4df590fe6a128aad9d0ce8063235eb2b328c2dc6a23c1ec5",
|
||||
header: "03000080",
|
||||
nVersionGroupId: "7082c403",
|
||||
nVersionGroupID: "7082c403",
|
||||
nLockTime: "481cdd86",
|
||||
nExpiryHeight: "b3cc4318",
|
||||
vin: nil,
|
||||
|
@ -83,7 +83,7 @@ var zip143tests = []txTestVector{
|
|||
//raw: "we have some raw data for this tx, which this comment is too small to contain",
|
||||
txid: "39fe585a56b005f568c3171d22afa916e946e2a8aff5971d58ee8a6fc1482059",
|
||||
header: "03000080",
|
||||
nVersionGroupId: "7082c403",
|
||||
nVersionGroupID: "7082c403",
|
||||
nLockTime: "97b0e4e4",
|
||||
nExpiryHeight: "c705fc05",
|
||||
vin: [][]string{
|
||||
|
@ -242,9 +242,9 @@ func subTestCommonBlockMeta(tt *txTestVector, tx *Transaction, t *testing.T, cas
|
|||
return false
|
||||
}
|
||||
|
||||
versionGroupBytes, _ := hex.DecodeString(tt.nVersionGroupId)
|
||||
versionGroupBytes, _ := hex.DecodeString(tt.nVersionGroupID)
|
||||
versionGroup := binary.LittleEndian.Uint32(versionGroupBytes)
|
||||
if versionGroup != tx.nVersionGroupId {
|
||||
if versionGroup != tx.nVersionGroupID {
|
||||
t.Errorf("Test %d: unexpected versionGroupId", caseNum)
|
||||
return false
|
||||
}
|
||||
|
@ -514,7 +514,7 @@ var zip243tests = []txTestVector{
|
|||
{
|
||||
txid: "5fc4867a1b8bd5ab709799adf322a85d10607e053726d5f5ab4b1c9ab897e6bc",
|
||||
header: "04000080",
|
||||
nVersionGroupId: "85202f89",
|
||||
nVersionGroupID: "85202f89",
|
||||
vin: nil,
|
||||
vout: [][]string{
|
||||
{"e7719811893e0000", "095200ac6551ac636565"},
|
||||
|
@ -617,7 +617,7 @@ var zip243tests = []txTestVector{
|
|||
{
|
||||
txid: "6732cf8d67aac5b82a2a0f0217a7d4aa245b2adb0b97fd2d923dfc674415e221",
|
||||
header: "04000080",
|
||||
nVersionGroupId: "85202f89",
|
||||
nVersionGroupID: "85202f89",
|
||||
vin: [][]string{
|
||||
{"56e551406a7ee8355656a21e43e38ce129fdadb759eddfa08f00fc8e567cef93", "c6792d01", "0763656300ac63ac", "8df04245"},
|
||||
{"1a33590d3e8cf49b2627218f0c292fa66ada945fa55bb23548e33a83a562957a", "3149a993", "086a5352516a65006a", "78d97ce4"},
|
||||
|
|
|
@ -74,13 +74,19 @@ func main() {
|
|||
fmt.Sprintf("%02x", (curHeight>>16)&0xFF)+
|
||||
fmt.Sprintf("%02x", (curHeight>>24)&0xFF), 1)
|
||||
|
||||
numTransactions := 1 // coinbase
|
||||
var numTransactions uint = 1 // coinbase
|
||||
allTransactionsHex := ""
|
||||
for scan.Scan() { // each line (hex-encoded transaction)
|
||||
transaction := scan.Bytes()
|
||||
allTransactionsHex += string(transaction)
|
||||
allTransactionsHex += scan.Text()
|
||||
numTransactions++
|
||||
}
|
||||
if err = scan.Err(); err != nil {
|
||||
panic("line too long!")
|
||||
}
|
||||
if numTransactions > 65535 {
|
||||
panic(fmt.Sprint("too many transactions ", numTransactions,
|
||||
" maximum 65535"))
|
||||
}
|
||||
|
||||
hashOfTxnsAndHeight := sha256.Sum256([]byte(allTransactionsHex + "#" + string(curHeight)))
|
||||
|
||||
|
@ -101,16 +107,18 @@ func main() {
|
|||
}
|
||||
|
||||
headerBytes, err := blockHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
panic(fmt.Sprint("Cannot marshal block header: ", err))
|
||||
}
|
||||
fmt.Print(hex.EncodeToString(headerBytes))
|
||||
|
||||
// After the header, there's a compactsize representation of the number of transactions.
|
||||
if numTransactions >= 253 {
|
||||
panic("Sorry, this tool doesn't support more than 253 transactions per block.")
|
||||
if numTransactions < 253 {
|
||||
fmt.Printf("%02x", numTransactions)
|
||||
} else {
|
||||
fmt.Printf("%02x%02x%02x", 253, numTransactions%256, numTransactions/256)
|
||||
}
|
||||
fmt.Printf("%s%02x%s%s\n",
|
||||
hex.EncodeToString(headerBytes),
|
||||
numTransactions,
|
||||
fakeCoinbase,
|
||||
allTransactionsHex)
|
||||
fmt.Printf("%s%s\n", fakeCoinbase, allTransactionsHex)
|
||||
|
||||
curHeight++
|
||||
prevhash = blockHeader.GetEncodableHash()
|
||||
|
|
|
@ -630,8 +630,8 @@ type DarksideStreamerClient interface {
|
|||
// StageBlocksCreate is like the previous two, except it creates 'count'
|
||||
// empty blocks at consecutive heights starting at height 'height'. The
|
||||
// 'nonce' is part of the header, so it contributes to the block hash; this
|
||||
// lets you create two fake blocks with the same transactions (or no
|
||||
// transactions) and same height, with two different hashes.
|
||||
// lets you create identical blocks (same transactions and height), but with
|
||||
// different hashes.
|
||||
StageBlocksCreate(ctx context.Context, in *DarksideEmptyBlocks, opts ...grpc.CallOption) (*Empty, error)
|
||||
// StageTransactionsStream stores the given transaction-height pairs in the
|
||||
// staging area until ApplyStaged() is called. Note that these transactions
|
||||
|
@ -639,20 +639,21 @@ type DarksideStreamerClient interface {
|
|||
// appear in a "mined" block (contained in the active blockchain presented
|
||||
// by the mock zcashd).
|
||||
StageTransactionsStream(ctx context.Context, opts ...grpc.CallOption) (DarksideStreamer_StageTransactionsStreamClient, error)
|
||||
// StageTransactions is the same except the transactions are fetched
|
||||
// from the given url. They are all staged into the block at the given
|
||||
// height. Staging transactions at multiple different heights requires
|
||||
// multiple calls.
|
||||
// StageTransactions is the same except the transactions are fetched from
|
||||
// the given url. They are all staged into the block at the given height.
|
||||
// Staging transactions to different heights requires multiple calls.
|
||||
StageTransactions(ctx context.Context, in *DarksideTransactionsURL, opts ...grpc.CallOption) (*Empty, error)
|
||||
// ApplyStaged iterates the list of blocks that were staged by the
|
||||
// StageBlocks*() gRPCs, in the order they were staged, and "merges" each
|
||||
// into the active, working blocks list that the mock zcashd is presenting
|
||||
// to lightwalletd. The resulting working block list can't have gaps; if the
|
||||
// working block range is 1000-1006, and the staged block range is 1003-1004,
|
||||
// the resulting range is 1000-1004, with 1000-1002 unchanged, blocks
|
||||
// 1003-1004 from the new range, and 1005-1006 dropped. After merging all
|
||||
// blocks, ApplyStaged() appends staged transactions (in the order received)
|
||||
// into each one's corresponding block. The staging area is then cleared.
|
||||
// to lightwalletd. Even as each block is applied, the active list can't
|
||||
// have gaps; if the active block range is 1000-1006, and the staged block
|
||||
// range is 1003-1004, the resulting range is 1000-1004, with 1000-1002
|
||||
// unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped.
|
||||
//
|
||||
// After merging all blocks, ApplyStaged() appends staged transactions (in
|
||||
// the order received) into each one's corresponding (by height) block
|
||||
// The staging area is then cleared.
|
||||
//
|
||||
// The argument specifies the latest block height that mock zcashd reports
|
||||
// (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
|
||||
|
@ -853,8 +854,8 @@ type DarksideStreamerServer interface {
|
|||
// StageBlocksCreate is like the previous two, except it creates 'count'
|
||||
// empty blocks at consecutive heights starting at height 'height'. The
|
||||
// 'nonce' is part of the header, so it contributes to the block hash; this
|
||||
// lets you create two fake blocks with the same transactions (or no
|
||||
// transactions) and same height, with two different hashes.
|
||||
// lets you create identical blocks (same transactions and height), but with
|
||||
// different hashes.
|
||||
StageBlocksCreate(context.Context, *DarksideEmptyBlocks) (*Empty, error)
|
||||
// StageTransactionsStream stores the given transaction-height pairs in the
|
||||
// staging area until ApplyStaged() is called. Note that these transactions
|
||||
|
@ -862,20 +863,21 @@ type DarksideStreamerServer interface {
|
|||
// appear in a "mined" block (contained in the active blockchain presented
|
||||
// by the mock zcashd).
|
||||
StageTransactionsStream(DarksideStreamer_StageTransactionsStreamServer) error
|
||||
// StageTransactions is the same except the transactions are fetched
|
||||
// from the given url. They are all staged into the block at the given
|
||||
// height. Staging transactions at multiple different heights requires
|
||||
// multiple calls.
|
||||
// StageTransactions is the same except the transactions are fetched from
|
||||
// the given url. They are all staged into the block at the given height.
|
||||
// Staging transactions to different heights requires multiple calls.
|
||||
StageTransactions(context.Context, *DarksideTransactionsURL) (*Empty, error)
|
||||
// ApplyStaged iterates the list of blocks that were staged by the
|
||||
// StageBlocks*() gRPCs, in the order they were staged, and "merges" each
|
||||
// into the active, working blocks list that the mock zcashd is presenting
|
||||
// to lightwalletd. The resulting working block list can't have gaps; if the
|
||||
// working block range is 1000-1006, and the staged block range is 1003-1004,
|
||||
// the resulting range is 1000-1004, with 1000-1002 unchanged, blocks
|
||||
// 1003-1004 from the new range, and 1005-1006 dropped. After merging all
|
||||
// blocks, ApplyStaged() appends staged transactions (in the order received)
|
||||
// into each one's corresponding block. The staging area is then cleared.
|
||||
// to lightwalletd. Even as each block is applied, the active list can't
|
||||
// have gaps; if the active block range is 1000-1006, and the staged block
|
||||
// range is 1003-1004, the resulting range is 1000-1004, with 1000-1002
|
||||
// unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped.
|
||||
//
|
||||
// After merging all blocks, ApplyStaged() appends staged transactions (in
|
||||
// the order received) into each one's corresponding (by height) block
|
||||
// The staging area is then cleared.
|
||||
//
|
||||
// The argument specifies the latest block height that mock zcashd reports
|
||||
// (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
|
||||
|
|
|
@ -69,8 +69,8 @@ service DarksideStreamer {
|
|||
// StageBlocksCreate is like the previous two, except it creates 'count'
|
||||
// empty blocks at consecutive heights starting at height 'height'. The
|
||||
// 'nonce' is part of the header, so it contributes to the block hash; this
|
||||
// lets you create two fake blocks with the same transactions (or no
|
||||
// transactions) and same height, with two different hashes.
|
||||
// lets you create identical blocks (same transactions and height), but with
|
||||
// different hashes.
|
||||
rpc StageBlocksCreate(DarksideEmptyBlocks) returns (Empty) {}
|
||||
|
||||
// StageTransactionsStream stores the given transaction-height pairs in the
|
||||
|
@ -80,21 +80,22 @@ service DarksideStreamer {
|
|||
// by the mock zcashd).
|
||||
rpc StageTransactionsStream(stream RawTransaction) returns (Empty) {}
|
||||
|
||||
// StageTransactions is the same except the transactions are fetched
|
||||
// from the given url. They are all staged into the block at the given
|
||||
// height. Staging transactions at multiple different heights requires
|
||||
// multiple calls.
|
||||
// StageTransactions is the same except the transactions are fetched from
|
||||
// the given url. They are all staged into the block at the given height.
|
||||
// Staging transactions to different heights requires multiple calls.
|
||||
rpc StageTransactions(DarksideTransactionsURL) returns (Empty) {}
|
||||
|
||||
// ApplyStaged iterates the list of blocks that were staged by the
|
||||
// StageBlocks*() gRPCs, in the order they were staged, and "merges" each
|
||||
// into the active, working blocks list that the mock zcashd is presenting
|
||||
// to lightwalletd. The resulting working block list can't have gaps; if the
|
||||
// working block range is 1000-1006, and the staged block range is 1003-1004,
|
||||
// the resulting range is 1000-1004, with 1000-1002 unchanged, blocks
|
||||
// 1003-1004 from the new range, and 1005-1006 dropped. After merging all
|
||||
// blocks, ApplyStaged() appends staged transactions (in the order received)
|
||||
// into each one's corresponding block. The staging area is then cleared.
|
||||
// to lightwalletd. Even as each block is applied, the active list can't
|
||||
// have gaps; if the active block range is 1000-1006, and the staged block
|
||||
// range is 1003-1004, the resulting range is 1000-1004, with 1000-1002
|
||||
// unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped.
|
||||
//
|
||||
// After merging all blocks, ApplyStaged() appends staged transactions (in
|
||||
// the order received) into each one's corresponding (by height) block
|
||||
// The staging area is then cleared.
|
||||
//
|
||||
// The argument specifies the latest block height that mock zcashd reports
|
||||
// (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
|
||||
|
|
Loading…
Reference in New Issue