increase the 252 per-block transaction limit (#273)

This commit is contained in:
Larry Ruane 2020-06-03 18:58:26 -06:00 committed by GitHub
parent e665ebe951
commit faca1ecbef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 261 additions and 130 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ test-log
lwd-api.html
*.orig
__debug_bin
.vscode

View File

@ -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())
}

View File

@ -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

View File

@ -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 &#39;count&#39;
empty blocks at consecutive heights starting at height &#39;height&#39;. The
&#39;nonce&#39; 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 &#34;merges&#34; each
into the active, working blocks list that the mock zcashd is presenting
to lightwalletd. The resulting working block list can&#39;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&#39;s corresponding block. The staging area is then cleared.
to lightwalletd. Even as each block is applied, the active list can&#39;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&#39;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&#39;s returned by GetLatestBlock). Note that ApplyStaged() can

View File

@ -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
}

View File

@ -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)

View File

@ -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}

View File

@ -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

View File

@ -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

View File

@ -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),

View File

@ -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"},

View File

@ -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()

View File

@ -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

View File

@ -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