// Copyright (c) 2017-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package blockchain

import (
	"fmt"

	"github.com/decred/dcrd/blockchain/stake/v5"
	"github.com/decred/dcrd/blockchain/standalone/v2"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/wire"
)

// SequenceLock represents the minimum timestamp and minimum block height after
// which a transaction can be included into a block while satisfying the
// relative lock times of all of its input sequence numbers.  It is calculated
// via the CalcSequenceLock function.  Each field may be -1 if none of the input
// sequence numbers require a specific relative lock time for the respective
// type.  Since all valid heights and times are larger than -1, this implies
// that it will not prevent a transaction from being included due to the
// sequence lock, which is the desired behavior.
type SequenceLock struct {
	MinHeight int64
	MinTime   int64
}

// isStakeBaseTx determines whether or not a transaction is a stakebase (also
// known as a vote).  A stakebase is a special transaction created by the
// proof-of-stake system that creates subsidy.  This function simply delegates
// to the IsSSGen function in the stake package and exists to make calling code
// that does not care about the specific reason the transaction is not a
// stakebase, rather only if it is one or not.
func isStakeBaseTx(tx *wire.MsgTx) bool {
	return stake.IsSSGen(tx)
}

// calcSequenceLock computes the relative lock times for the passed transaction
// from the point of view of the block node passed in as the first argument.
//
// See the CalcSequenceLock comments for more details.
func (b *BlockChain) calcSequenceLock(node *blockNode, tx *dcrutil.Tx, view *UtxoViewpoint, isActive bool) (*SequenceLock, error) {
	isTreasuryEnabled, err := b.isTreasuryAgendaActive(node.parent)
	if err != nil {
		return nil, err
	}

	// A value of -1 for each lock type allows a transaction to be included
	// in a block at any given height or time.
	sequenceLock := &SequenceLock{MinHeight: -1, MinTime: -1}

	// Sequence locks do not apply if they are not yet active, the tx
	// version is less than 2, or the tx is a coinbase or stakebase, so
	// return now with a sequence lock that indicates the tx can possibly be
	// included in a block at any given height or time.
	msgTx := tx.MsgTx()
	enforce := isActive && msgTx.Version >= 2
	if !enforce || standalone.IsCoinBaseTx(msgTx, isTreasuryEnabled) ||
		isStakeBaseTx(msgTx) {
		return sequenceLock, nil
	}

	for txInIndex, txIn := range msgTx.TxIn {
		// Nothing to calculate for this input when relative time locks
		// are disabled for it.
		sequenceNum := txIn.Sequence
		if sequenceNum&wire.SequenceLockTimeDisabled != 0 {
			continue
		}

		utxo := view.LookupEntry(txIn.PreviousOutPoint)
		if utxo == nil {
			str := fmt.Sprintf("output %v referenced from "+
				"transaction %s:%d either does not exist or "+
				"has already been spent", txIn.PreviousOutPoint,
				tx.Hash(), txInIndex)
			return sequenceLock, ruleError(ErrMissingTxOut, str)
		}

		// Calculate the sequence locks from the point of view of the
		// next block for inputs that are in the mempool.
		inputHeight := utxo.BlockHeight()
		if inputHeight == 0x7fffffff {
			inputHeight = node.height + 1
		}

		// Mask off the value portion of the sequence number to obtain
		// the time lock delta required before this input can be spent.
		// The relative lock can be time based or block based.
		relativeLock := int64(sequenceNum & wire.SequenceLockTimeMask)

		if sequenceNum&wire.SequenceLockTimeIsSeconds != 0 {
			// This input requires a time based relative lock
			// expressed in seconds before it can be spent and time
			// based locks are calculated relative to the earliest
			// possible time the block that contains the referenced
			// output could have been.  That time is the past
			// median time of the block before it (technically one
			// second after that, but that complexity is ignored for
			// time based locks which already have a granularity
			// associated with them anyways).  Therefore, the block
			// prior to the one in which the referenced output was
			// included is needed to compute its past median time.
			prevInputHeight := inputHeight - 1
			if prevInputHeight < 0 {
				prevInputHeight = 0
			}
			blockNode := node.Ancestor(prevInputHeight)
			medianTime := blockNode.CalcPastMedianTime()

			// Calculate the minimum required timestamp based on the
			// sum of the aforementioned past median time and
			// required relative number of seconds.  Since time
			// based relative locks have a granularity associated
			// with them, shift left accordingly in order to convert
			// to the proper number of relative seconds.  Also,
			// subtract one from the relative lock to maintain the
			// original lock time semantics.
			relativeSecs := relativeLock << wire.SequenceLockTimeGranularity
			minTime := medianTime.Unix() + relativeSecs - 1
			if minTime > sequenceLock.MinTime {
				sequenceLock.MinTime = minTime
			}
		} else {
			// This input requires a relative lock expressed in
			// blocks before it can be spent.  Therefore, calculate
			// the minimum required height based on the sum of the
			// input height and required relative number of blocks.
			// Also, subtract one from the relative lock in order to
			// maintain the original lock time semantics.
			minHeight := inputHeight + relativeLock - 1
			if minHeight > sequenceLock.MinHeight {
				sequenceLock.MinHeight = minHeight
			}
		}
	}

	return sequenceLock, nil
}

// CalcSequenceLock computes the minimum block height and time after which the
// passed transaction can be included into a block while satisfying the relative
// lock times of all of its input sequence numbers.  The passed view is used to
// obtain the past median time and block heights of the blocks in which the
// referenced outputs of the inputs to the transaction were included.  The
// generated sequence lock can be used in conjunction with a block height and
// median time to determine if all inputs to the transaction have reached the
// required maturity allowing it to be included in a block.
//
// NOTE: This will calculate the sequence locks regardless of the state of the
// agenda which conditionally activates it.  This is acceptable for standard
// transactions, however, callers which are intending to perform any type of
// consensus checking must check the status of the agenda first.
//
// This function is safe for concurrent access.
func (b *BlockChain) CalcSequenceLock(tx *dcrutil.Tx, view *UtxoViewpoint) (*SequenceLock, error) {
	b.chainLock.Lock()
	seqLock, err := b.calcSequenceLock(b.bestChain.Tip(), tx, view, true)
	b.chainLock.Unlock()
	return seqLock, err
}

// LockTimeToSequence converts the passed relative lock time to a sequence
// number in accordance with DCP0003.
//
// A sequence number is defined as follows:
//
//   - bit 31 is the disable bit
//   - the next 8 bits are reserved
//   - bit 22 is the relative lock type (unset = block height, set = seconds)
//   - the next 6 bits are reserved
//   - the least significant 16 bits represent the value
//   - value has a granularity of 512 when interpreted as seconds (bit 22 set)
//
// Diagram
//
//	---------------------------------------------------
//	| Disable | Reserved |  Type | Reserved |  Value  |
//	|-------------------------------------------------|
//	|  1 bit  |  8 bits  | 1 bit |  6 bits  | 16 bits |
//	|-------------------------------------------------|
//	|   [31]  |  [30-23] |  [22] |  [21-16] | [15-0]  |
//	---------------------------------------------------
//
// The above implies that the maximum relative block height that can be encoded
// is 65535 and the maximum relative number of seconds that can be encoded is
// 65535*512 = 33,553,920 seconds (~1.06 years).  It also means that seconds are
// truncated to the nearest granularity towards 0 (e.g. 536 seconds will end up
// round tripping as 512 seconds and 1500 seconds will end up round tripping as
// 1024 seconds).
//
// An error will be returned for values that are larger than can be represented.
func LockTimeToSequence(isSeconds bool, lockTime uint32) (uint32, error) {
	// The corresponding sequence number is simply the desired input age
	// when expressing the relative lock time in blocks.
	if !isSeconds {
		if lockTime > wire.SequenceLockTimeMask {
			return 0, fmt.Errorf("max relative block height a "+
				"sequence number can represent is %d",
				wire.SequenceLockTimeMask)
		}
		return lockTime, nil
	}

	maxSeconds := uint32(wire.SequenceLockTimeMask <<
		wire.SequenceLockTimeGranularity)
	if lockTime > maxSeconds {
		return 0, fmt.Errorf("max relative seconds a sequence number "+
			"can represent is %d", maxSeconds)
	}

	// Set the 22nd bit which indicates the lock time is in seconds, then
	// shift the lock time over by 9 since the time granularity is in
	// 512-second intervals (2^9). This results in a max lock time of
	// 33,553,920 seconds (~1.06 years).
	return wire.SequenceLockTimeIsSeconds |
		lockTime>>wire.SequenceLockTimeGranularity, nil
}
