diff --git a/chain/chain.go b/chain/chain.go index ddf0d69..7ec0dad 100644 --- a/chain/chain.go +++ b/chain/chain.go @@ -19,6 +19,9 @@ type Chain struct { } // New creates a Chain backed by the given store. +// +// s, _ := store.New("~/.lethean/chain/chain.db") +// blockchain := chain.New(s) func New(s *store.Store) *Chain { return &Chain{store: s} } diff --git a/chain/difficulty.go b/chain/difficulty.go index 1eca220..a4ca382 100644 --- a/chain/difficulty.go +++ b/chain/difficulty.go @@ -76,12 +76,16 @@ func (c *Chain) nextDifficultyWith(height uint64, forks []config.HardFork, baseT // NextDifficulty computes the expected PoW difficulty for the block at the // given height. Pre-HF6 the target is 120s; post-HF6 it doubles to 240s. +// +// diff, err := blockchain.NextDifficulty(nextHeight, config.MainnetForks) func (c *Chain) NextDifficulty(height uint64, forks []config.HardFork) (uint64, error) { return c.nextDifficultyWith(height, forks, config.DifficultyPowTarget, config.DifficultyPowTargetHF6) } // NextPoSDifficulty computes the expected PoS difficulty for the block at the // given height. Pre-HF6 the target is 120s; post-HF6 it doubles to 240s. +// +// diff, err := blockchain.NextPoSDifficulty(nextHeight, config.MainnetForks) func (c *Chain) NextPoSDifficulty(height uint64, forks []config.HardFork) (uint64, error) { return c.nextDifficultyWith(height, forks, config.DifficultyPosTarget, config.DifficultyPosTargetHF6) } diff --git a/chain/ring.go b/chain/ring.go index ae34916..d34466c 100644 --- a/chain/ring.go +++ b/chain/ring.go @@ -17,6 +17,8 @@ import ( // GetRingOutputs fetches the public keys for the given global output indices // at the specified spending height and amount. This implements the // consensus.RingOutputsFn signature for use during signature verification. +// +// keys, err := blockchain.GetRingOutputs(blockHeight, inputAmount, []uint64{0, 5, 12, 30}) func (c *Chain) GetRingOutputs(height, amount uint64, offsets []uint64) ([]types.PublicKey, error) { publicKeys := make([]types.PublicKey, len(offsets)) for i, gidx := range offsets { @@ -81,6 +83,8 @@ func ringOutputSpendKey(height uint64, target types.TxOutTarget) (types.PublicKe // consensus.ZCRingOutputsFn signature for post-HF4 CLSAG GGX verification. // // ZC outputs are indexed at amount=0 (confidential amounts). +// +// members, err := blockchain.GetZCRingOutputs([]uint64{100, 200, 300}) func (c *Chain) GetZCRingOutputs(offsets []uint64) ([]consensus.ZCRingMember, error) { members := make([]consensus.ZCRingMember, len(offsets)) for i, gidx := range offsets { diff --git a/consensus/balance.go b/consensus/balance.go index 6c10772..1e120e9 100644 --- a/consensus/balance.go +++ b/consensus/balance.go @@ -13,6 +13,8 @@ import "dappco.re/go/core/blockchain/crypto" // The caller is responsible for constructing the balance context point(s) // from transaction inputs, outputs, fees, and any asset-operation terms. // This helper only performs the cryptographic check. +// +// ok := consensus.VerifyBalanceProof(txHash, false, pointA, pointB, proofBytes) func VerifyBalanceProof(hash [32]byte, aIsX bool, a [32]byte, b [32]byte, proof []byte) bool { return crypto.VerifyDoubleSchnorr(hash, aIsX, a, b, proof) } diff --git a/consensus/block.go b/consensus/block.go index ce7ea86..0c92355 100644 --- a/consensus/block.go +++ b/consensus/block.go @@ -23,6 +23,8 @@ func IsPoS(flags uint8) bool { // CheckTimestamp validates a block's timestamp against future limits and // the median of recent timestamps. +// +// consensus.CheckTimestamp(blk.Timestamp, blk.Flags, uint64(time.Now().Unix()), recentTimestamps) func CheckTimestamp(blockTimestamp uint64, flags uint8, adjustedTime uint64, recentTimestamps []uint64) error { // Future time limit. limit := config.BlockFutureTimeLimit @@ -77,6 +79,8 @@ func expectedMinerTxVersion(forks []config.HardFork, height uint64) uint64 { // ValidateMinerTx checks the structure of a coinbase (miner) transaction. // For PoW blocks: exactly 1 input (TxInputGenesis). For PoS blocks: exactly // 2 inputs (TxInputGenesis + stake input). +// +// consensus.ValidateMinerTx(&blk.MinerTx, height, config.MainnetForks) func ValidateMinerTx(tx *types.Transaction, height uint64, forks []config.HardFork) error { expectedVersion := expectedMinerTxVersion(forks, height) if tx.Version != expectedVersion { @@ -132,6 +136,8 @@ func ValidateMinerTx(tx *types.Transaction, height uint64, forks []config.HardFo // // Post-HF4 miner transactions may use Zarcanum outputs, so the validator // sums both transparent amounts and the encoded Zarcanum amount field. +// +// consensus.ValidateBlockReward(&blk.MinerTx, height, blockSize, medianSize, totalFees, config.MainnetForks) func ValidateBlockReward(minerTx *types.Transaction, height, blockSize, medianSize, totalFees uint64, forks []config.HardFork) error { base := BaseReward(height) reward, err := BlockReward(base, blockSize, medianSize) @@ -193,6 +199,8 @@ func checkBlockVersion(majorVersion uint8, forks []config.HardFork, height uint6 // CheckBlockVersion validates that the block's major version matches the // expected version for the supplied height and fork schedule. +// +// consensus.CheckBlockVersion(blk.MajorVersion, config.MainnetForks, height) func CheckBlockVersion(majorVersion uint8, forks []config.HardFork, height uint64) error { return checkBlockVersion(majorVersion, forks, height) } @@ -201,6 +209,8 @@ func CheckBlockVersion(majorVersion uint8, forks []config.HardFork, height uint6 // the block version, timestamp, miner transaction structure, and reward. // Transaction semantic validation for regular transactions should be done // separately via ValidateTransaction for each tx in the block. +// +// consensus.ValidateBlock(&blk, height, blockSize, medianSize, totalFees, adjustedTime, recentTimestamps, config.MainnetForks) func ValidateBlock(blk *types.Block, height, blockSize, medianSize, totalFees, adjustedTime uint64, recentTimestamps []uint64, forks []config.HardFork) error { @@ -238,6 +248,8 @@ func ValidateBlock(blk *types.Block, height, blockSize, medianSize, totalFees, a // // Returns false if the fork version is not found or if the activation height // is too low for a meaningful freeze window. +// +// if consensus.IsPreHardforkFreeze(config.TestnetForks, config.HF5, height) { /* reject non-coinbase txs */ } func IsPreHardforkFreeze(forks []config.HardFork, version uint8, height uint64) bool { activationHeight, ok := config.HardforkActivationHeight(forks, version) if !ok { @@ -262,6 +274,8 @@ func IsPreHardforkFreeze(forks []config.HardFork, version uint8, height uint64) // pre-hardfork freeze check. This wraps ValidateTransaction with an // additional check: during the freeze window before HF5, non-coinbase // transactions are rejected. +// +// consensus.ValidateTransactionInBlock(&tx, txBlob, config.MainnetForks, blockHeight) func ValidateTransactionInBlock(tx *types.Transaction, txBlob []byte, forks []config.HardFork, height uint64) error { // Pre-hardfork freeze: reject non-coinbase transactions in the freeze window. if !isCoinbase(tx) && IsPreHardforkFreeze(forks, config.HF5, height) { diff --git a/consensus/fee.go b/consensus/fee.go index b6f5f7d..df1f885 100644 --- a/consensus/fee.go +++ b/consensus/fee.go @@ -17,6 +17,8 @@ import ( // TxFee calculates the transaction fee for pre-HF4 (v0/v1) transactions. // Coinbase transactions return 0. For standard transactions, fee equals // the difference between total input amounts and total output amounts. +// +// fee, err := consensus.TxFee(&tx) func TxFee(tx *types.Transaction) (uint64, error) { if isCoinbase(tx) { return 0, nil diff --git a/consensus/pow.go b/consensus/pow.go index 2be459a..74c300d 100644 --- a/consensus/pow.go +++ b/consensus/pow.go @@ -19,6 +19,8 @@ var maxTarget = new(big.Int).Lsh(big.NewInt(1), 256) // CheckDifficulty returns true if hash meets the given difficulty target. // The hash (interpreted as a 256-bit little-endian number) must be less // than maxTarget / difficulty. +// +// if consensus.CheckDifficulty(powHash, currentDifficulty) { /* valid PoW solution */ } func CheckDifficulty(hash types.Hash, difficulty uint64) bool { if difficulty == 0 { return true @@ -39,6 +41,8 @@ func CheckDifficulty(hash types.Hash, difficulty uint64) bool { // CheckPoWHash computes the RandomX hash of a block header hash + nonce // and checks it against the difficulty target. +// +// valid, err := consensus.CheckPoWHash(headerHash, nonce, difficulty) func CheckPoWHash(headerHash types.Hash, nonce, difficulty uint64) (bool, error) { // Build input: header_hash (32 bytes) || nonce (8 bytes LE). var input [40]byte diff --git a/consensus/reward.go b/consensus/reward.go index f76e7b0..db4e9ff 100644 --- a/consensus/reward.go +++ b/consensus/reward.go @@ -17,6 +17,8 @@ import ( // BaseReward returns the base block reward at the given height. // Height 0 (genesis) returns the premine amount. All other heights // return the fixed block reward (1 LTHN). +// +// reward := consensus.BaseReward(15000) // 1_000_000_000_000 (1 LTHN) func BaseReward(height uint64) uint64 { if height == 0 { return config.Premine @@ -33,6 +35,8 @@ func BaseReward(height uint64) uint64 { // reward = baseReward * (2*median - size) * size / median² // // Uses math/bits.Mul64 for 128-bit intermediate products to avoid overflow. +// +// reward, err := consensus.BlockReward(consensus.BaseReward(height), blockSize, medianSize) func BlockReward(baseReward, blockSize, medianSize uint64) (uint64, error) { effectiveMedian := medianSize if effectiveMedian < config.BlockGrantedFullRewardZone { @@ -72,6 +76,9 @@ func BlockReward(baseReward, blockSize, medianSize uint64) (uint64, error) { // MinerReward calculates the total miner payout. Pre-HF4, transaction // fees are added to the base reward. Post-HF4 (postHF4=true), fees are // burned and the miner receives only the base reward. +// +// payout := consensus.MinerReward(reward, totalFees, false) // pre-HF4: reward + fees +// payout := consensus.MinerReward(reward, totalFees, true) // post-HF4: reward only (fees burned) func MinerReward(baseReward, totalFees uint64, postHF4 bool) uint64 { if postHF4 { return baseReward diff --git a/consensus/tx.go b/consensus/tx.go index 19c3b0a..8fcfdb4 100644 --- a/consensus/tx.go +++ b/consensus/tx.go @@ -33,6 +33,8 @@ func newTransactionForkState(forks []config.HardFork, height uint64) transaction // ValidateTransaction performs semantic validation on a regular (non-coinbase) // transaction. Checks are ordered to match the C++ validate_tx_semantic(). +// +// consensus.ValidateTransaction(&tx, txBlob, config.MainnetForks, blockHeight) func ValidateTransaction(tx *types.Transaction, txBlob []byte, forks []config.HardFork, height uint64) error { state := newTransactionForkState(forks, height) diff --git a/consensus/verify.go b/consensus/verify.go index 4b6080b..ee154ca 100644 --- a/consensus/verify.go +++ b/consensus/verify.go @@ -41,6 +41,9 @@ type ZCRingOutputsFn func(offsets []uint64) ([]ZCRingMember, error) // getRingOutputs is used for pre-HF4 (V1) signature verification. // getZCRingOutputs is used for post-HF4 (V2) CLSAG GGX verification. // Either may be nil for structural-only checks. +// +// consensus.VerifyTransactionSignatures(&tx, config.MainnetForks, height, chain.GetRingOutputs, chain.GetZCRingOutputs) +// consensus.VerifyTransactionSignatures(&tx, config.MainnetForks, height, nil, nil) // structural only func VerifyTransactionSignatures(tx *types.Transaction, forks []config.HardFork, height uint64, getRingOutputs RingOutputsFn, getZCRingOutputs ZCRingOutputsFn) error { diff --git a/types/asset.go b/types/asset.go index f7427b8..4c105e4 100644 --- a/types/asset.go +++ b/types/asset.go @@ -8,6 +8,8 @@ package types import ( "fmt" "unicode/utf8" + + coreerr "dappco.re/go/core/log" ) // AssetDescriptorOperationTag is the wire tag for asset_descriptor_operation @@ -37,24 +39,27 @@ type AssetDescriptorBase struct { } // Validate checks that the base asset metadata is structurally valid. +// +// base := types.AssetDescriptorBase{Ticker: "LTHN", FullName: "Lethean", TotalMaxSupply: 1_000_000, OwnerKey: ownerPub} +// if err := base.Validate(); err != nil { ... } func (base AssetDescriptorBase) Validate() error { tickerLen := utf8.RuneCountInString(base.Ticker) fullNameLen := utf8.RuneCountInString(base.FullName) if base.TotalMaxSupply == 0 { - return fmt.Errorf("types: total max supply must be non-zero") + return coreerr.E("AssetDescriptorBase.Validate", "total max supply must be non-zero", nil) } if base.CurrentSupply > base.TotalMaxSupply { - return fmt.Errorf("types: current supply %d exceeds max supply %d", base.CurrentSupply, base.TotalMaxSupply) + return coreerr.E("AssetDescriptorBase.Validate", fmt.Sprintf("current supply %d exceeds max supply %d", base.CurrentSupply, base.TotalMaxSupply), nil) } if tickerLen == 0 || tickerLen > 6 { - return fmt.Errorf("types: ticker length %d out of range", tickerLen) + return coreerr.E("AssetDescriptorBase.Validate", fmt.Sprintf("ticker length %d out of range [1,6]", tickerLen), nil) } if fullNameLen == 0 || fullNameLen > 64 { - return fmt.Errorf("types: full name length %d out of range", fullNameLen) + return coreerr.E("AssetDescriptorBase.Validate", fmt.Sprintf("full name length %d out of range [1,64]", fullNameLen), nil) } if base.OwnerKey.IsZero() { - return fmt.Errorf("types: owner key must be non-zero") + return coreerr.E("AssetDescriptorBase.Validate", "owner key must be non-zero", nil) } return nil @@ -73,62 +78,65 @@ type AssetDescriptorOperation struct { } // Validate checks that the operation is structurally valid for HF5 parsing. +// +// op := types.AssetDescriptorOperation{Version: 1, OperationType: types.AssetOpRegister, Descriptor: &base} +// if err := op.Validate(); err != nil { ... } func (op AssetDescriptorOperation) Validate() error { switch op.Version { case 0, 1: default: - return fmt.Errorf("types: unsupported version %d", op.Version) + return coreerr.E("AssetDescriptorOperation.Validate", fmt.Sprintf("unsupported version %d", op.Version), nil) } switch op.OperationType { case AssetOpRegister: if !op.AssetID.IsZero() { - return fmt.Errorf("types: register operation must not carry asset id") + return coreerr.E("AssetDescriptorOperation.Validate", "register operation must not carry asset id", nil) } if op.Descriptor == nil { - return fmt.Errorf("types: register operation missing descriptor") + return coreerr.E("AssetDescriptorOperation.Validate", "register operation missing descriptor", nil) } if err := op.Descriptor.Validate(); err != nil { return err } if op.AmountToEmit != 0 || op.AmountToBurn != 0 { - return fmt.Errorf("types: register operation must not include emission or burn amounts") + return coreerr.E("AssetDescriptorOperation.Validate", "register operation must not include emission or burn amounts", nil) } case AssetOpEmit: if op.AssetID.IsZero() { - return fmt.Errorf("types: emit operation must carry asset id") + return coreerr.E("AssetDescriptorOperation.Validate", "emit operation must carry asset id", nil) } if op.AmountToEmit == 0 { - return fmt.Errorf("types: emit operation has zero amount") + return coreerr.E("AssetDescriptorOperation.Validate", "emit operation has zero amount", nil) } if op.Descriptor != nil { - return fmt.Errorf("types: emit operation must not carry descriptor") + return coreerr.E("AssetDescriptorOperation.Validate", "emit operation must not carry descriptor", nil) } case AssetOpUpdate: if op.AssetID.IsZero() { - return fmt.Errorf("types: update operation must carry asset id") + return coreerr.E("AssetDescriptorOperation.Validate", "update operation must carry asset id", nil) } if op.Descriptor == nil { - return fmt.Errorf("types: update operation missing descriptor") + return coreerr.E("AssetDescriptorOperation.Validate", "update operation missing descriptor", nil) } if err := op.Descriptor.Validate(); err != nil { return err } if op.AmountToEmit != 0 || op.AmountToBurn != 0 { - return fmt.Errorf("types: update operation must not include emission or burn amounts") + return coreerr.E("AssetDescriptorOperation.Validate", "update operation must not include emission or burn amounts", nil) } case AssetOpBurn, AssetOpPublicBurn: if op.AssetID.IsZero() { - return fmt.Errorf("types: burn operation must carry asset id") + return coreerr.E("AssetDescriptorOperation.Validate", "burn operation must carry asset id", nil) } if op.AmountToBurn == 0 { - return fmt.Errorf("types: burn operation has zero amount") + return coreerr.E("AssetDescriptorOperation.Validate", "burn operation has zero amount", nil) } if op.Descriptor != nil { - return fmt.Errorf("types: burn operation must not carry descriptor") + return coreerr.E("AssetDescriptorOperation.Validate", "burn operation must not carry descriptor", nil) } default: - return fmt.Errorf("types: unsupported operation type %d", op.OperationType) + return coreerr.E("AssetDescriptorOperation.Validate", fmt.Sprintf("unsupported operation type %d", op.OperationType), nil) } return nil