Skip to content

Commit

Permalink
May 2022 Hardfork (#499)
Browse files Browse the repository at this point in the history
* Add Script64BitIntegers script flag

 This commit adds a flag to the script engine to use 64 bit integers and updates
 the ScriptNum class to optionally support 64 bit integers.

 This is the first part of the May 2022 hardfork.

* Add ScriptNumber 64 bit tests

* Create native introspection opcodes

* Check for script flag in new opcodes

* Update script_tests.json

* Add opcode tests

* Add fork activation

* Add activation tests

* Create testnet4 params

* Bump version

* Copy opcode slices before returning

* Add op_mul and fix overflow handling

* Disable op_mul before fork

* update scriptnum and makeScriptNum documentation for 64-bit cases (#501)

* Fix typos

Co-authored-by: emergent_reasons <[email protected]>
  • Loading branch information
cpacia and emergent-reasons authored May 5, 2022
1 parent bcb8a8a commit 58741b0
Show file tree
Hide file tree
Showing 42 changed files with 2,036 additions and 188 deletions.
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()
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
Loading

0 comments on commit 58741b0

Please sign in to comment.