Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

May 2022 Hardfork #499

Merged
merged 15 commits into from
May 5, 2022
1 change: 1 addition & 0 deletions bchec/genprecomps.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// This file is ignored during the regular build due to the following build tag.
// It is called by go generate and used to automatically generate pre-computed
// tables used to accelerate operations.
//go:build ignore
// +build ignore

package main
Expand Down
1 change: 1 addition & 0 deletions bchec/gensecp256k1.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// This file is ignored during the regular build due to the following build tag.
// This build tag is set during go generate.
//go:build gensecp256k1
// +build gensecp256k1

package bchec
Expand Down
1 change: 1 addition & 0 deletions bchrpc/proxy/gw_tools.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build tools
// +build tools

package main
Expand Down
107 changes: 107 additions & 0 deletions blockchain/fullblocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,3 +714,110 @@ func TestPhononActivation(t *testing.T) {
}
}
}

// TestCosmicInflationActivation tests that 64 bit integers and native introspection
// opcodes activate correctly.
func TestCosmicInflationActivation(t *testing.T) {
tests, err := fullblocktests.GenerateCosmicInflationBlocks()
if err != nil {
t.Fatalf("failed to generate tests: %v", err)
}

// Create a new database and chain instance to run tests against.
params := &chaincfg.RegressionNetParams
params.CosmicInflationActivationTime = 0
params.PhononForkHeight = 0
params.GravitonForkHeight = 0
params.MagneticAnonomalyForkHeight = 0
params.UahfForkHeight = 0
chain, teardownFunc, err := chainSetup("fullblocktest",
params, 32000000)
if err != nil {
t.Errorf("Failed to setup chain instance: %v", err)
return
}
defer teardownFunc()

// testAcceptedBlock attempts to process the block in the provided test
// instance and ensures that it was accepted according to the flags
// specified in the test.
testAcceptedBlock := func(item fullblocktests.AcceptedBlock) {
blockHeight := item.Height
block := bchutil.NewBlock(item.Block)
block.SetHeight(blockHeight)
t.Logf("Testing block %s (hash %s, height %d)",
item.Name, block.Hash(), blockHeight)

isMainChain, isOrphan, err := chain.ProcessBlock(block,
blockchain.BFNone)
if err != nil {
t.Fatalf("block %q (hash %s, height %d) should "+
"have been accepted: %v", item.Name,
block.Hash(), blockHeight, err)
}

// Ensure the main chain and orphan flags match the values
// specified in the test.
if isMainChain != item.IsMainChain {
t.Fatalf("block %q (hash %s, height %d) unexpected main "+
"chain flag -- got %v, want %v", item.Name,
block.Hash(), blockHeight, isMainChain,
item.IsMainChain)
}
if isOrphan != item.IsOrphan {
t.Fatalf("block %q (hash %s, height %d) unexpected "+
"orphan flag -- got %v, want %v", item.Name,
block.Hash(), blockHeight, isOrphan,
item.IsOrphan)
}
}

// testRejectedBlock attempts to process the block in the provided test
// instance and ensures that it was rejected with the reject code
// specified in the test.
testRejectedBlock := func(item fullblocktests.RejectedBlock) {
blockHeight := item.Height
block := bchutil.NewBlock(item.Block)
block.SetHeight(blockHeight)
t.Logf("Testing block %s (hash %s, height %d)",
item.Name, block.Hash(), blockHeight)

_, _, err := chain.ProcessBlock(block, blockchain.BFNone)
if err == nil {
t.Fatalf("block %q (hash %s, height %d) should not "+
"have been accepted", item.Name, block.Hash(),
blockHeight)
}

// Ensure the error code is of the expected type and the reject
// code matches the value specified in the test instance.
rerr, ok := err.(blockchain.RuleError)
if !ok {
t.Fatalf("block %q (hash %s, height %d) returned "+
"unexpected error type -- got %T, want "+
"blockchain.RuleError", item.Name, block.Hash(),
blockHeight, err)
}
if rerr.ErrorCode != item.RejectCode {
t.Fatalf("block %q (hash %s, height %d) does not have "+
"expected reject code -- got %v, want %v",
item.Name, block.Hash(), blockHeight,
rerr.ErrorCode, item.RejectCode)
}
}

for testNum, test := range tests {
for itemNum, item := range test {
switch item := item.(type) {
case fullblocktests.AcceptedBlock:
testAcceptedBlock(item)
case fullblocktests.RejectedBlock:
testRejectedBlock(item)
default:
t.Fatalf("test #%d, item #%d is not one of "+
"the supported test instance types -- "+
"got type: %T", testNum, itemNum, item)
}
}
}
}
195 changes: 195 additions & 0 deletions blockchain/fullblocktests/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2852,3 +2852,198 @@ func GeneratePhononBlocks() (tests [][]TestInstance, err error) {
})
return tests, nil
}

// GenerateCosmicInflationBlocks generates a test chain containing several
// 64 bit integer and native introspection transactions per block.
func GenerateCosmicInflationBlocks() (tests [][]TestInstance, err error) {
// In order to simplify the generation code which really should never
// fail unless the test code itself is broken, panics are used
// internally. This deferred func ensures any panics don't escape the
// generator by replacing the named error return with the underlying
// panic error.
defer func() {
if r := recover(); r != nil {
tests = nil

switch rt := r.(type) {
case string:
err = errors.New(rt)
case error:
err = rt
default:
err = errors.New("Unknown panic")
}
}
}()

// Create a test generator instance initialized with the genesis block
// as the tip.
g, err := makeTestGenerator(regressionNetParams)
if err != nil {
return nil, err
}

acceptBlock := func(blockName string, block *wire.MsgBlock, isMainChain, isOrphan bool) TestInstance {
blockHeight := g.blockHeights[blockName]
return AcceptedBlock{blockName, block, blockHeight, isMainChain,
isOrphan}
}

coinbaseMaturity := g.params.CoinbaseMaturity
var testInstances []TestInstance
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bm%d", i)
g.nextBlock(blockName, nil)
g.saveTipCoinbaseOut()
testInstances = append(testInstances, acceptBlock(g.tipName,
g.tip, true, false))
}
tests = append(tests, testInstances)

// Collect spendable outputs. This simplifies the code below.
var outs []*spendableOut
for i := uint16(0); i < coinbaseMaturity; i++ {
op := g.oldestCoinbaseOut()
outs = append(outs, &op)
}

add64BitInteger := func(block *wire.MsgBlock) {
// unsortedTxs is a slice of bchutil.Tx's that we will sort later using CTOR.
var unsortedTxs []*bchutil.Tx

// Let's first add any txs (excluding the coinbase) into unsortedTxs
for _, tx := range block.Transactions[1:] {
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx))
}

// Create one tx paying a p2sh output
tx1 := createSpendTx(outs[0], 0)
builder := txscript.NewScriptBuilder().
AddOp(txscript.OP_1).
AddOp(txscript.OP_ADD).
AddInt64(1152921504606846976).
AddOp(txscript.OP_EQUAL)
redeemScript, err := builder.Script()
if err != nil {
panic(err)
}

addr, err := bchutil.NewAddressScriptHash(redeemScript, &chaincfg.RegressionNetParams)
if err != nil {
panic(err)
}
script, err := txscript.PayToAddrScript(addr)
if err != nil {
panic(err)
}
tx1.TxOut[0].PkScript = script

block.AddTransaction(tx1)
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx1))

// Spend from tx1
so1 := &spendableOut{
amount: bchutil.Amount(tx1.TxOut[0].Value),
prevOut: wire.OutPoint{
Hash: tx1.TxHash(),
Index: 0,
},
}

tx2 := createSpendTx(so1, 0)
scriptSig, err := txscript.NewScriptBuilder().
AddInt64(1152921504606846975).
AddData(redeemScript).Script()
Comment on lines +2920 to +2956
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah shit sorry. This suggestion (deleted) just came out as a big red block. It just has 3 line changes:

		sixtyBitInt := int64(1152921504606846976)
...
			AddInt64(sixtyBitInt).
...
			AddInt64(sixtyBitInt - 1).

I suggest it because it took me a while to realize the two scripts are connected.

Could add an op_1 op_mul too (or in replacement of add) if you want to exercise both 64 and the re-enabled op together.

if err != nil {
panic(err)
}
tx2.TxIn[0].SignatureScript = scriptSig
block.AddTransaction(tx2)
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx2))

// Sort the unsortedTxs slice
sort.Sort(mining.TxSorter(unsortedTxs))
// Set block.Transactions to only the coinbase
block.Transactions = block.Transactions[:1]

// Add each tx from our (now sorted) slice
for _, tx := range unsortedTxs {
block.Transactions = append(block.Transactions, tx.MsgTx())
}
}

addIntrospection := func(block *wire.MsgBlock) {
// unsortedTxs is a slice of bchutil.Tx's that we will sort later using CTOR.
var unsortedTxs []*bchutil.Tx

// Let's first add any txs (excluding the coinbase) into unsortedTxs
for _, tx := range block.Transactions[1:] {
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx))
}

// Create one tx paying a p2sh output
tx1 := createSpendTx(outs[1], 0)
builder := txscript.NewScriptBuilder().
AddData(make([]byte, 20)).
AddOp(txscript.OP_DROP).
AddOp(txscript.OP_ACTIVEBYTECODE).
AddOp(txscript.OP_EQUAL)
redeemScript, err := builder.Script()
if err != nil {
panic(err)
}

addr, err := bchutil.NewAddressScriptHash(redeemScript, &chaincfg.RegressionNetParams)
if err != nil {
panic(err)
}
script, err := txscript.PayToAddrScript(addr)
if err != nil {
panic(err)
}
tx1.TxOut[0].PkScript = script

block.AddTransaction(tx1)
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx1))

// Spend from tx1
so1 := &spendableOut{
amount: bchutil.Amount(tx1.TxOut[0].Value),
prevOut: wire.OutPoint{
Hash: tx1.TxHash(),
Index: 0,
},
}

tx2 := createSpendTx(so1, 0)
scriptSig, err := txscript.NewScriptBuilder().
AddData(redeemScript).
AddData(redeemScript).Script()
if err != nil {
panic(err)
}
tx2.TxIn[0].SignatureScript = scriptSig
block.AddTransaction(tx2)
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx2))

// Sort the unsortedTxs slice
sort.Sort(mining.TxSorter(unsortedTxs))
// Set block.Transactions to only the coinbase
block.Transactions = block.Transactions[:1]

// Add each tx from our (now sorted) slice
for _, tx := range unsortedTxs {
block.Transactions = append(block.Transactions, tx.MsgTx())
}
}

g.nextBlock("b0", nil, add64BitInteger)
tests = append(tests, []TestInstance{
acceptBlock(g.tipName, g.tip, true, false),
})
g.nextBlock("b1", nil, addIntrospection)
tests = append(tests, []TestInstance{
acceptBlock(g.tipName, g.tip, true, false),
})
return tests, nil
}
23 changes: 22 additions & 1 deletion blockchain/scriptval.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,30 @@ out:
sigScript := txIn.SignatureScript
pkScript := utxo.PkScript()
inputAmount := utxo.Amount()

utxoEntryCache := txscript.NewUtxoCache()
for i, in := range txVI.tx.MsgTx().TxIn {
if i == txVI.txInIndex {
utxoEntryCache.AddEntry(i, *wire.NewTxOut(utxo.amount, utxo.pkScript))
continue
}
u := v.utxoView.LookupEntry(in.PreviousOutPoint)
if u == nil {
str := fmt.Sprintf("unable to find unspent "+
"output %v referenced from "+
"transaction %s:%d",
in.PreviousOutPoint, txVI.tx.Hash(),
i)
err := ruleError(ErrMissingTxOut, str)
v.sendResult(err)
break out
}
utxoEntryCache.AddEntry(i, *wire.NewTxOut(u.amount, u.pkScript))
}

vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes,
inputAmount)
utxoEntryCache, inputAmount)
if err != nil {
str := fmt.Sprintf("failed to parse input "+
"%s:%d which references output %v - "+
Expand Down
8 changes: 7 additions & 1 deletion blockchain/thresholdstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package blockchain

import (
"fmt"

"github.com/gcash/bchd/chaincfg"
"github.com/gcash/bchd/chaincfg/chainhash"
)

Expand Down Expand Up @@ -297,6 +297,12 @@ func (b *BlockChain) IsDeploymentActive(deploymentID uint32) (bool, error) {
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) deploymentState(prevNode *blockNode, deploymentID uint32) (ThresholdState, error) {
if deploymentID == chaincfg.DeploymentCSV && b.chainParams.CSVHeight > 0 {
if prevNode.height+1 >= b.chainParams.CSVHeight {
return ThresholdActive, nil
}
}

if deploymentID > uint32(len(b.chainParams.Deployments)) {
return ThresholdFailed, DeploymentError(deploymentID)
}
Expand Down