Skip to content

Commit 58741b0

Browse files
May 2022 Hardfork (#499)
* 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 <34025062+emergent-reasons@users.noreply.github.com>
1 parent bcb8a8a commit 58741b0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2036
-188
lines changed

bchec/genprecomps.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// This file is ignored during the regular build due to the following build tag.
66
// It is called by go generate and used to automatically generate pre-computed
77
// tables used to accelerate operations.
8+
//go:build ignore
89
// +build ignore
910

1011
package main

bchec/gensecp256k1.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

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

910
package bchec

bchrpc/proxy/gw_tools.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build tools
12
// +build tools
23

34
package main

blockchain/fullblocks_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,3 +714,110 @@ func TestPhononActivation(t *testing.T) {
714714
}
715715
}
716716
}
717+
718+
// TestCosmicInflationActivation tests that 64 bit integers and native introspection
719+
// opcodes activate correctly.
720+
func TestCosmicInflationActivation(t *testing.T) {
721+
tests, err := fullblocktests.GenerateCosmicInflationBlocks()
722+
if err != nil {
723+
t.Fatalf("failed to generate tests: %v", err)
724+
}
725+
726+
// Create a new database and chain instance to run tests against.
727+
params := &chaincfg.RegressionNetParams
728+
params.CosmicInflationActivationTime = 0
729+
params.PhononForkHeight = 0
730+
params.GravitonForkHeight = 0
731+
params.MagneticAnonomalyForkHeight = 0
732+
params.UahfForkHeight = 0
733+
chain, teardownFunc, err := chainSetup("fullblocktest",
734+
params, 32000000)
735+
if err != nil {
736+
t.Errorf("Failed to setup chain instance: %v", err)
737+
return
738+
}
739+
defer teardownFunc()
740+
741+
// testAcceptedBlock attempts to process the block in the provided test
742+
// instance and ensures that it was accepted according to the flags
743+
// specified in the test.
744+
testAcceptedBlock := func(item fullblocktests.AcceptedBlock) {
745+
blockHeight := item.Height
746+
block := bchutil.NewBlock(item.Block)
747+
block.SetHeight(blockHeight)
748+
t.Logf("Testing block %s (hash %s, height %d)",
749+
item.Name, block.Hash(), blockHeight)
750+
751+
isMainChain, isOrphan, err := chain.ProcessBlock(block,
752+
blockchain.BFNone)
753+
if err != nil {
754+
t.Fatalf("block %q (hash %s, height %d) should "+
755+
"have been accepted: %v", item.Name,
756+
block.Hash(), blockHeight, err)
757+
}
758+
759+
// Ensure the main chain and orphan flags match the values
760+
// specified in the test.
761+
if isMainChain != item.IsMainChain {
762+
t.Fatalf("block %q (hash %s, height %d) unexpected main "+
763+
"chain flag -- got %v, want %v", item.Name,
764+
block.Hash(), blockHeight, isMainChain,
765+
item.IsMainChain)
766+
}
767+
if isOrphan != item.IsOrphan {
768+
t.Fatalf("block %q (hash %s, height %d) unexpected "+
769+
"orphan flag -- got %v, want %v", item.Name,
770+
block.Hash(), blockHeight, isOrphan,
771+
item.IsOrphan)
772+
}
773+
}
774+
775+
// testRejectedBlock attempts to process the block in the provided test
776+
// instance and ensures that it was rejected with the reject code
777+
// specified in the test.
778+
testRejectedBlock := func(item fullblocktests.RejectedBlock) {
779+
blockHeight := item.Height
780+
block := bchutil.NewBlock(item.Block)
781+
block.SetHeight(blockHeight)
782+
t.Logf("Testing block %s (hash %s, height %d)",
783+
item.Name, block.Hash(), blockHeight)
784+
785+
_, _, err := chain.ProcessBlock(block, blockchain.BFNone)
786+
if err == nil {
787+
t.Fatalf("block %q (hash %s, height %d) should not "+
788+
"have been accepted", item.Name, block.Hash(),
789+
blockHeight)
790+
}
791+
792+
// Ensure the error code is of the expected type and the reject
793+
// code matches the value specified in the test instance.
794+
rerr, ok := err.(blockchain.RuleError)
795+
if !ok {
796+
t.Fatalf("block %q (hash %s, height %d) returned "+
797+
"unexpected error type -- got %T, want "+
798+
"blockchain.RuleError", item.Name, block.Hash(),
799+
blockHeight, err)
800+
}
801+
if rerr.ErrorCode != item.RejectCode {
802+
t.Fatalf("block %q (hash %s, height %d) does not have "+
803+
"expected reject code -- got %v, want %v",
804+
item.Name, block.Hash(), blockHeight,
805+
rerr.ErrorCode, item.RejectCode)
806+
}
807+
}
808+
809+
for testNum, test := range tests {
810+
for itemNum, item := range test {
811+
switch item := item.(type) {
812+
case fullblocktests.AcceptedBlock:
813+
testAcceptedBlock(item)
814+
case fullblocktests.RejectedBlock:
815+
testRejectedBlock(item)
816+
default:
817+
t.Fatalf("test #%d, item #%d is not one of "+
818+
"the supported test instance types -- "+
819+
"got type: %T", testNum, itemNum, item)
820+
}
821+
}
822+
}
823+
}

blockchain/fullblocktests/generate.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2852,3 +2852,198 @@ func GeneratePhononBlocks() (tests [][]TestInstance, err error) {
28522852
})
28532853
return tests, nil
28542854
}
2855+
2856+
// GenerateCosmicInflationBlocks generates a test chain containing several
2857+
// 64 bit integer and native introspection transactions per block.
2858+
func GenerateCosmicInflationBlocks() (tests [][]TestInstance, err error) {
2859+
// In order to simplify the generation code which really should never
2860+
// fail unless the test code itself is broken, panics are used
2861+
// internally. This deferred func ensures any panics don't escape the
2862+
// generator by replacing the named error return with the underlying
2863+
// panic error.
2864+
defer func() {
2865+
if r := recover(); r != nil {
2866+
tests = nil
2867+
2868+
switch rt := r.(type) {
2869+
case string:
2870+
err = errors.New(rt)
2871+
case error:
2872+
err = rt
2873+
default:
2874+
err = errors.New("Unknown panic")
2875+
}
2876+
}
2877+
}()
2878+
2879+
// Create a test generator instance initialized with the genesis block
2880+
// as the tip.
2881+
g, err := makeTestGenerator(regressionNetParams)
2882+
if err != nil {
2883+
return nil, err
2884+
}
2885+
2886+
acceptBlock := func(blockName string, block *wire.MsgBlock, isMainChain, isOrphan bool) TestInstance {
2887+
blockHeight := g.blockHeights[blockName]
2888+
return AcceptedBlock{blockName, block, blockHeight, isMainChain,
2889+
isOrphan}
2890+
}
2891+
2892+
coinbaseMaturity := g.params.CoinbaseMaturity
2893+
var testInstances []TestInstance
2894+
for i := uint16(0); i < coinbaseMaturity; i++ {
2895+
blockName := fmt.Sprintf("bm%d", i)
2896+
g.nextBlock(blockName, nil)
2897+
g.saveTipCoinbaseOut()
2898+
testInstances = append(testInstances, acceptBlock(g.tipName,
2899+
g.tip, true, false))
2900+
}
2901+
tests = append(tests, testInstances)
2902+
2903+
// Collect spendable outputs. This simplifies the code below.
2904+
var outs []*spendableOut
2905+
for i := uint16(0); i < coinbaseMaturity; i++ {
2906+
op := g.oldestCoinbaseOut()
2907+
outs = append(outs, &op)
2908+
}
2909+
2910+
add64BitInteger := func(block *wire.MsgBlock) {
2911+
// unsortedTxs is a slice of bchutil.Tx's that we will sort later using CTOR.
2912+
var unsortedTxs []*bchutil.Tx
2913+
2914+
// Let's first add any txs (excluding the coinbase) into unsortedTxs
2915+
for _, tx := range block.Transactions[1:] {
2916+
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx))
2917+
}
2918+
2919+
// Create one tx paying a p2sh output
2920+
tx1 := createSpendTx(outs[0], 0)
2921+
builder := txscript.NewScriptBuilder().
2922+
AddOp(txscript.OP_1).
2923+
AddOp(txscript.OP_ADD).
2924+
AddInt64(1152921504606846976).
2925+
AddOp(txscript.OP_EQUAL)
2926+
redeemScript, err := builder.Script()
2927+
if err != nil {
2928+
panic(err)
2929+
}
2930+
2931+
addr, err := bchutil.NewAddressScriptHash(redeemScript, &chaincfg.RegressionNetParams)
2932+
if err != nil {
2933+
panic(err)
2934+
}
2935+
script, err := txscript.PayToAddrScript(addr)
2936+
if err != nil {
2937+
panic(err)
2938+
}
2939+
tx1.TxOut[0].PkScript = script
2940+
2941+
block.AddTransaction(tx1)
2942+
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx1))
2943+
2944+
// Spend from tx1
2945+
so1 := &spendableOut{
2946+
amount: bchutil.Amount(tx1.TxOut[0].Value),
2947+
prevOut: wire.OutPoint{
2948+
Hash: tx1.TxHash(),
2949+
Index: 0,
2950+
},
2951+
}
2952+
2953+
tx2 := createSpendTx(so1, 0)
2954+
scriptSig, err := txscript.NewScriptBuilder().
2955+
AddInt64(1152921504606846975).
2956+
AddData(redeemScript).Script()
2957+
if err != nil {
2958+
panic(err)
2959+
}
2960+
tx2.TxIn[0].SignatureScript = scriptSig
2961+
block.AddTransaction(tx2)
2962+
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx2))
2963+
2964+
// Sort the unsortedTxs slice
2965+
sort.Sort(mining.TxSorter(unsortedTxs))
2966+
// Set block.Transactions to only the coinbase
2967+
block.Transactions = block.Transactions[:1]
2968+
2969+
// Add each tx from our (now sorted) slice
2970+
for _, tx := range unsortedTxs {
2971+
block.Transactions = append(block.Transactions, tx.MsgTx())
2972+
}
2973+
}
2974+
2975+
addIntrospection := func(block *wire.MsgBlock) {
2976+
// unsortedTxs is a slice of bchutil.Tx's that we will sort later using CTOR.
2977+
var unsortedTxs []*bchutil.Tx
2978+
2979+
// Let's first add any txs (excluding the coinbase) into unsortedTxs
2980+
for _, tx := range block.Transactions[1:] {
2981+
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx))
2982+
}
2983+
2984+
// Create one tx paying a p2sh output
2985+
tx1 := createSpendTx(outs[1], 0)
2986+
builder := txscript.NewScriptBuilder().
2987+
AddData(make([]byte, 20)).
2988+
AddOp(txscript.OP_DROP).
2989+
AddOp(txscript.OP_ACTIVEBYTECODE).
2990+
AddOp(txscript.OP_EQUAL)
2991+
redeemScript, err := builder.Script()
2992+
if err != nil {
2993+
panic(err)
2994+
}
2995+
2996+
addr, err := bchutil.NewAddressScriptHash(redeemScript, &chaincfg.RegressionNetParams)
2997+
if err != nil {
2998+
panic(err)
2999+
}
3000+
script, err := txscript.PayToAddrScript(addr)
3001+
if err != nil {
3002+
panic(err)
3003+
}
3004+
tx1.TxOut[0].PkScript = script
3005+
3006+
block.AddTransaction(tx1)
3007+
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx1))
3008+
3009+
// Spend from tx1
3010+
so1 := &spendableOut{
3011+
amount: bchutil.Amount(tx1.TxOut[0].Value),
3012+
prevOut: wire.OutPoint{
3013+
Hash: tx1.TxHash(),
3014+
Index: 0,
3015+
},
3016+
}
3017+
3018+
tx2 := createSpendTx(so1, 0)
3019+
scriptSig, err := txscript.NewScriptBuilder().
3020+
AddData(redeemScript).
3021+
AddData(redeemScript).Script()
3022+
if err != nil {
3023+
panic(err)
3024+
}
3025+
tx2.TxIn[0].SignatureScript = scriptSig
3026+
block.AddTransaction(tx2)
3027+
unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx2))
3028+
3029+
// Sort the unsortedTxs slice
3030+
sort.Sort(mining.TxSorter(unsortedTxs))
3031+
// Set block.Transactions to only the coinbase
3032+
block.Transactions = block.Transactions[:1]
3033+
3034+
// Add each tx from our (now sorted) slice
3035+
for _, tx := range unsortedTxs {
3036+
block.Transactions = append(block.Transactions, tx.MsgTx())
3037+
}
3038+
}
3039+
3040+
g.nextBlock("b0", nil, add64BitInteger)
3041+
tests = append(tests, []TestInstance{
3042+
acceptBlock(g.tipName, g.tip, true, false),
3043+
})
3044+
g.nextBlock("b1", nil, addIntrospection)
3045+
tests = append(tests, []TestInstance{
3046+
acceptBlock(g.tipName, g.tip, true, false),
3047+
})
3048+
return tests, nil
3049+
}

blockchain/scriptval.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,30 @@ out:
7777
sigScript := txIn.SignatureScript
7878
pkScript := utxo.PkScript()
7979
inputAmount := utxo.Amount()
80+
81+
utxoEntryCache := txscript.NewUtxoCache()
82+
for i, in := range txVI.tx.MsgTx().TxIn {
83+
if i == txVI.txInIndex {
84+
utxoEntryCache.AddEntry(i, *wire.NewTxOut(utxo.amount, utxo.pkScript))
85+
continue
86+
}
87+
u := v.utxoView.LookupEntry(in.PreviousOutPoint)
88+
if u == nil {
89+
str := fmt.Sprintf("unable to find unspent "+
90+
"output %v referenced from "+
91+
"transaction %s:%d",
92+
in.PreviousOutPoint, txVI.tx.Hash(),
93+
i)
94+
err := ruleError(ErrMissingTxOut, str)
95+
v.sendResult(err)
96+
break out
97+
}
98+
utxoEntryCache.AddEntry(i, *wire.NewTxOut(u.amount, u.pkScript))
99+
}
100+
80101
vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
81102
txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes,
82-
inputAmount)
103+
utxoEntryCache, inputAmount)
83104
if err != nil {
84105
str := fmt.Sprintf("failed to parse input "+
85106
"%s:%d which references output %v - "+

blockchain/thresholdstate.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package blockchain
66

77
import (
88
"fmt"
9-
9+
"github.com/gcash/bchd/chaincfg"
1010
"github.com/gcash/bchd/chaincfg/chainhash"
1111
)
1212

@@ -297,6 +297,12 @@ func (b *BlockChain) IsDeploymentActive(deploymentID uint32) (bool, error) {
297297
//
298298
// This function MUST be called with the chain state lock held (for writes).
299299
func (b *BlockChain) deploymentState(prevNode *blockNode, deploymentID uint32) (ThresholdState, error) {
300+
if deploymentID == chaincfg.DeploymentCSV && b.chainParams.CSVHeight > 0 {
301+
if prevNode.height+1 >= b.chainParams.CSVHeight {
302+
return ThresholdActive, nil
303+
}
304+
}
305+
300306
if deploymentID > uint32(len(b.chainParams.Deployments)) {
301307
return ThresholdFailed, DeploymentError(deploymentID)
302308
}

0 commit comments

Comments
 (0)