Skip to content

Commit 357dca4

Browse files
committed
fix handling PATFO
1 parent 41fe76b commit 357dca4

9 files changed

Lines changed: 91 additions & 40 deletions

File tree

blockchain/cashtokens_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,11 @@ func TestCashTokensBeforeActivationStandardInvalidOPCodes(t *testing.T) {
455455
if len(test) == 7 {
456456
txIdx = int(test[6].(float64))
457457
}
458+
459+
if blockchain.IsPATFO(utxos[txIdx].TokenData, utxos[txIdx].PkScript, 100, 782772) {
460+
continue
461+
}
462+
458463
inputAmount := utxos[txIdx].Value
459464

460465
vm, err := txscript.NewEngine(utxos[txIdx].PkScript, &tx, txIdx, flags, nil, nil, cache, inputAmount)

blockchain/scriptval.go

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ type txValidateItem struct {
2929
// inputs. It provides several channels for communication and a processing
3030
// function that is intended to be in run multiple goroutines.
3131
type txValidator struct {
32-
validateChan chan *txValidateItem
33-
quitChan chan struct{}
34-
resultChan chan error
35-
utxoView *UtxoViewpoint
36-
flags txscript.ScriptFlags
37-
sigCache *txscript.SigCache
38-
hashCache *txscript.HashCache
39-
sigChecks uint32
40-
maxSigChecks uint32
32+
validateChan chan *txValidateItem
33+
quitChan chan struct{}
34+
resultChan chan error
35+
utxoView *UtxoViewpoint
36+
flags txscript.ScriptFlags
37+
sigCache *txscript.SigCache
38+
hashCache *txscript.HashCache
39+
sigChecks uint32
40+
maxSigChecks uint32
41+
upgrade9ForkHeight int32
4142
}
4243

4344
// sendResult sends the result of a script pair validation on the internal
@@ -98,9 +99,29 @@ out:
9899
utxoEntryCache.AddEntry(i, *wire.NewTxOut(u.amount, u.pkScript, u.tokenData))
99100
}
100101

101-
_, err := wire.RunCashTokensValidityAlgorithm(utxoEntryCache, txVI.tx.MsgTx())
102-
if err != nil {
102+
isPATFO := IsPATFO(
103+
utxo.tokenData, utxo.pkScript,
104+
utxo.blockHeight, v.upgrade9ForkHeight)
105+
106+
if isPATFO {
107+
// PATFOs are provably unspendable. The software ignores
108+
// other types of provably unspendable tokens so we use
109+
// the same behaviour here.
110+
str := fmt.Sprintf("unable to find unspent "+
111+
"output %v referenced from "+
112+
"transaction %s:%d",
113+
txIn.PreviousOutPoint, txVI.tx.Hash(),
114+
txVI.txInIndex)
115+
err := ruleError(ErrMissingTxOut, str)
103116
v.sendResult(err)
117+
break out
118+
}
119+
120+
if v.flags.HasFlag(txscript.ScriptAllowCashTokens) {
121+
_, err := wire.RunCashTokensValidityAlgorithm(utxoEntryCache, txVI.tx.MsgTx())
122+
if err != nil {
123+
v.sendResult(err)
124+
}
104125
}
105126

106127
vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
@@ -222,24 +243,25 @@ func (v *txValidator) Validate(items []*txValidateItem) error {
222243
// newTxValidator returns a new instance of txValidator to be used for
223244
// validating transaction scripts asynchronously.
224245
func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags,
225-
sigCache *txscript.SigCache, hashCache *txscript.HashCache, maxSigChecks uint32) *txValidator {
246+
sigCache *txscript.SigCache, hashCache *txscript.HashCache, maxSigChecks uint32, upgrade9ForkHeight int32) *txValidator {
226247
return &txValidator{
227-
validateChan: make(chan *txValidateItem),
228-
quitChan: make(chan struct{}),
229-
resultChan: make(chan error),
230-
utxoView: utxoView,
231-
sigCache: sigCache,
232-
hashCache: hashCache,
233-
flags: flags,
234-
maxSigChecks: maxSigChecks,
248+
validateChan: make(chan *txValidateItem),
249+
quitChan: make(chan struct{}),
250+
resultChan: make(chan error),
251+
utxoView: utxoView,
252+
sigCache: sigCache,
253+
hashCache: hashCache,
254+
flags: flags,
255+
maxSigChecks: maxSigChecks,
256+
upgrade9ForkHeight: upgrade9ForkHeight,
235257
}
236258
}
237259

238260
// ValidateTransactionScripts validates the scripts for the passed transaction
239261
// using multiple goroutines. It returns the number of sigchecks in the transaction.
240262
func ValidateTransactionScripts(tx *bchutil.Tx, utxoView *UtxoViewpoint,
241263
flags txscript.ScriptFlags, sigCache *txscript.SigCache,
242-
hashCache *txscript.HashCache) (uint32, error) {
264+
hashCache *txscript.HashCache, upgrade9ForkHeight int32) (uint32, error) {
243265

244266
// If the HashCache is present, and it doesn't yet contain the
245267
// partial sighashes for this transaction, then we add the
@@ -269,8 +291,9 @@ func ValidateTransactionScripts(tx *bchutil.Tx, utxoView *UtxoViewpoint,
269291
}
270292
utxoCache.AddEntry(i, *wire.NewTxOut(u.amount, u.pkScript, u.tokenData))
271293
}
272-
273-
cachedHashes.AddTxSigHashUtxoFromUtxoCache(tx.MsgTx(), utxoCache)
294+
if flags.HasFlag(txscript.ScriptAllowCashTokens) {
295+
cachedHashes.AddTxSigHashUtxoFromUtxoCache(tx.MsgTx(), utxoCache)
296+
}
274297
}
275298

276299
// Collect all of the transaction inputs and required information for
@@ -295,18 +318,32 @@ func ValidateTransactionScripts(tx *bchutil.Tx, utxoView *UtxoViewpoint,
295318
}
296319

297320
// Validate all of the inputs.
298-
validator := newTxValidator(utxoView, flags, sigCache, hashCache, 0)
321+
validator := newTxValidator(utxoView, flags, sigCache, hashCache, 0, upgrade9ForkHeight)
299322
if err := validator.Validate(txValItems); err != nil {
300323
return 0, err
301324
}
302325
return sigChecks, nil
303326
}
304327

328+
// Checks if the input contains pre-activation token-forgery output.
329+
// PATFOs are provably unspendable so a better place to check for them might be inside
330+
// txscript.IsUnspendable() but since we need to check for block heights, to mimimize
331+
// changes in the codebase, we handle it here. It might be a good idea to change this later.
332+
func IsPATFO(tokenData wire.TokenData, pkScript []byte, utxoBlockHeight int32, upgrade9ForkHeight int32) bool {
333+
isPATFO := false
334+
if !tokenData.IsEmpty() || pkScript[0] == 0xef {
335+
if utxoBlockHeight < upgrade9ForkHeight {
336+
isPATFO = true
337+
}
338+
}
339+
return isPATFO
340+
}
341+
305342
// checkBlockScripts executes and validates the scripts for all transactions in
306343
// the passed block using multiple goroutines.
307344
func checkBlockScripts(block *bchutil.Block, utxoView *UtxoViewpoint,
308345
scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache,
309-
hashCache *txscript.HashCache, maxSigChecks uint32) error {
346+
hashCache *txscript.HashCache, maxSigChecks uint32, upgrade9ForkHeight int32) error {
310347

311348
// Collect all of the transaction inputs and required information for
312349
// validation for all transactions in the block into a single slice.
@@ -346,8 +383,9 @@ func checkBlockScripts(block *bchutil.Block, utxoView *UtxoViewpoint,
346383
}
347384
utxoCache.AddEntry(i, *wire.NewTxOut(u.amount, u.pkScript, u.tokenData))
348385
}
349-
350-
cachedHashes.AddTxSigHashUtxoFromUtxoCache(tx.MsgTx(), utxoCache)
386+
if scriptFlags.HasFlag(txscript.ScriptAllowCashTokens) {
387+
cachedHashes.AddTxSigHashUtxoFromUtxoCache(tx.MsgTx(), utxoCache)
388+
}
351389
}
352390

353391
for txInIdx, txIn := range tx.MsgTx().TxIn {
@@ -368,7 +406,7 @@ func checkBlockScripts(block *bchutil.Block, utxoView *UtxoViewpoint,
368406
}
369407

370408
// Validate all of the inputs.
371-
validator := newTxValidator(utxoView, scriptFlags, sigCache, hashCache, maxSigChecks)
409+
validator := newTxValidator(utxoView, scriptFlags, sigCache, hashCache, maxSigChecks, upgrade9ForkHeight)
372410
start := time.Now()
373411
if err := validator.Validate(txValItems); err != nil {
374412
return err

blockchain/scriptval_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestCheckBlockScripts(t *testing.T) {
4141
}
4242

4343
scriptFlags := txscript.ScriptBip16
44-
err = checkBlockScripts(blocks[0], view, scriptFlags, nil, nil, 0)
44+
err = checkBlockScripts(blocks[0], view, scriptFlags, nil, nil, 0, 0)
4545
if err != nil {
4646
t.Errorf("Transaction script validation failed: %v\n", err)
4747
return

blockchain/validate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1256,7 +1256,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *bchutil.Block, vi
12561256
if runScripts {
12571257
maxSigChecks := b.excessiveBlockSize / BlockMaxBytesMaxSigChecksRatio
12581258
err := checkBlockScripts(block, view, scriptFlags, b.sigCache,
1259-
b.hashCache, maxSigChecks)
1259+
b.hashCache, maxSigChecks, b.chainParams.Upgrade9ForkHeight)
12601260
if err != nil {
12611261
return err
12621262
}

mempool/mempool.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *bchutil.Tx, isNew, rateLimit, rejec
955955
// Verify crypto signatures for each input and reject the transaction if
956956
// any don't verify.
957957
_, err = blockchain.ValidateTransactionScripts(tx, utxoView, scriptFlags,
958-
mp.cfg.SigCache, mp.cfg.HashCache)
958+
mp.cfg.SigCache, mp.cfg.HashCache, mp.cfg.ChainParams.Upgrade9ForkHeight)
959959
if err != nil {
960960
if cerr, ok := err.(blockchain.RuleError); ok {
961961
return nil, nil, chainRuleError(cerr)

mining/mining.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ mempoolLoop:
698698
}
699699
sigchecks, err := blockchain.ValidateTransactionScripts(tx, blockUtxos,
700700
txscript.StandardVerifyFlags, g.sigCache,
701-
g.hashCache)
701+
g.hashCache, g.chainParams.Upgrade9ForkHeight)
702702
if err != nil {
703703
log.Tracef("Skipping tx %s due to error in "+
704704
"ValidateTransactionScripts: %v", tx.Hash(), err)

txscript/opcode.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2384,7 +2384,9 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
23842384
sigHashes = NewTxSigHashes(&vm.tx)
23852385
}
23862386

2387-
sigHashes.AddTxSigHashUtxoFromUtxoCache(&vm.tx, vm.utxoCache)
2387+
if hashType&SigHashUTXO != 0 { // TODO TODO use hashType. For other places that use AddTxSigHashUtxoFromUtxoCache too
2388+
sigHashes.AddTxSigHashUtxoFromUtxoCache(&vm.tx, vm.utxoCache)
2389+
}
23882390

23892391
hash, err = calcSignatureHash(subScript, sigHashes, hashType, &vm.tx, vm.txIdx,
23902392
vm.inputAmount, vm.hasFlag(ScriptVerifyBip143SigHash))
@@ -2574,9 +2576,9 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
25742576
sigHashes = NewTxSigHashes(&vm.tx)
25752577
}
25762578
}
2577-
2578-
sigHashes.AddTxSigHashUtxoFromUtxoCache(&vm.tx, vm.utxoCache)
2579-
2579+
if vm.hasFlag(ScriptAllowCashTokens) {
2580+
sigHashes.AddTxSigHashUtxoFromUtxoCache(&vm.tx, vm.utxoCache)
2581+
}
25802582
success := true
25812583

25822584
if vm.hasFlag(ScriptVerifySchnorrMultisig) && len(dummy) > 0 { // Schnorr multisig

wire/cashtokens.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type TokenData struct {
3030
}
3131

3232
func (tokenData *TokenData) SeparateTokenDataFromPKScriptIfExists(buf []byte, pver uint32) ([]byte, error) {
33-
if buf[0] != PREFIX_BYTE {
33+
if len(buf) == 0 || buf[0] != PREFIX_BYTE {
3434
// There is no token data. Return the whole buffer as script
3535
return buf, nil
3636
} else {
@@ -201,7 +201,6 @@ func IsCoinBaseTx(msgTx *MsgTx) bool {
201201

202202
// Token Validation Algorithm
203203
func RunCashTokensValidityAlgorithm(cache utxoCacheInterface, tx *MsgTx) (bool, error) {
204-
205204
if IsCoinBaseTx(tx) {
206205
return false, messageError("RunCashTokensValidityAlgorithm", "ErrCashTokensValidation")
207206
}

wire/msgtx.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -783,10 +783,17 @@ func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) (int, error)
783783

784784
scriptAndTokendataSize := len(ScriptAndPossibleTokenData)
785785

786-
if ScriptAndPossibleTokenData[0] != 0xef { // Todo maybe change this
786+
if scriptAndTokendataSize > 0 && ScriptAndPossibleTokenData[0] != 0xef { // Todo maybe change this
787+
to.PkScript = ScriptAndPossibleTokenData
788+
return scriptAndTokendataSize, err
789+
}
790+
var tokenDataSeparationErr error
791+
to.PkScript, tokenDataSeparationErr = to.TokenData.SeparateTokenDataFromPKScriptIfExists(ScriptAndPossibleTokenData, pver)
792+
793+
if tokenDataSeparationErr != nil { // after activation this must fail! TODO TODO
794+
to.TokenData = TokenData{} // Maybe move this inside TokenData.SeparateTokenDataFromPKScriptIfExists()
787795
to.PkScript = ScriptAndPossibleTokenData
788796
}
789-
to.PkScript, err = to.TokenData.SeparateTokenDataFromPKScriptIfExists(ScriptAndPossibleTokenData, pver)
790797
return scriptAndTokendataSize, err
791798
}
792799

0 commit comments

Comments
 (0)