diff --git a/config/config.go b/config/config.go index c9c2298..e7bd041 100644 --- a/config/config.go +++ b/config/config.go @@ -173,6 +173,11 @@ const ( // transaction blob in bytes. CoinbaseBlobReservedSize uint64 = 1100 + // MaxTransactionBlobSize is the maximum serialised transaction size in bytes. + // Derived from BlockGrantedFullRewardZone - 2*CoinbaseBlobReservedSize + // but the canonical C++ value is 374,600. + MaxTransactionBlobSize uint64 = 374_600 + // BlockFutureTimeLimit is the maximum acceptable future timestamp for // PoW blocks in seconds (2 hours). BlockFutureTimeLimit uint64 = 60 * 60 * 2 diff --git a/consensus/doc.go b/consensus/doc.go new file mode 100644 index 0000000..ce1ae16 --- /dev/null +++ b/consensus/doc.go @@ -0,0 +1,19 @@ +// Copyright (c) 2017-2026 Lethean (https://lt.hn) +// +// Licensed under the European Union Public Licence (EUPL) version 1.2. +// SPDX-License-Identifier: EUPL-1.2 + +// Package consensus implements Lethean blockchain validation rules. +// +// Validation is organised in three layers: +// +// - Structural: transaction size, input/output counts, key image +// uniqueness. No cryptographic operations required. +// - Economic: block reward calculation, fee extraction, balance +// checks, overflow detection. +// - Cryptographic: PoW hash verification (RandomX via CGo), +// ring signature verification, proof verification. +// +// All functions take *config.ChainConfig and a block height for +// hardfork-aware validation. The package has no dependency on chain/. +package consensus diff --git a/consensus/errors.go b/consensus/errors.go new file mode 100644 index 0000000..0c0959d --- /dev/null +++ b/consensus/errors.go @@ -0,0 +1,38 @@ +// Copyright (c) 2017-2026 Lethean (https://lt.hn) +// +// Licensed under the European Union Public Licence (EUPL) version 1.2. +// SPDX-License-Identifier: EUPL-1.2 + +package consensus + +import "errors" + +// Sentinel errors for consensus validation failures. +var ( + // Transaction structural errors. + ErrTxTooLarge = errors.New("consensus: transaction too large") + ErrNoInputs = errors.New("consensus: transaction has no inputs") + ErrTooManyInputs = errors.New("consensus: transaction exceeds max inputs") + ErrInvalidInputType = errors.New("consensus: unsupported input type") + ErrNoOutputs = errors.New("consensus: transaction has no outputs") + ErrTooFewOutputs = errors.New("consensus: transaction below min outputs") + ErrTooManyOutputs = errors.New("consensus: transaction exceeds max outputs") + ErrInvalidOutput = errors.New("consensus: invalid output") + ErrDuplicateKeyImage = errors.New("consensus: duplicate key image in transaction") + ErrInvalidExtra = errors.New("consensus: invalid extra field") + + // Transaction economic errors. + ErrInputOverflow = errors.New("consensus: input amount overflow") + ErrOutputOverflow = errors.New("consensus: output amount overflow") + ErrNegativeFee = errors.New("consensus: outputs exceed inputs") + + // Block errors. + ErrBlockTooLarge = errors.New("consensus: block exceeds max size") + ErrTimestampFuture = errors.New("consensus: block timestamp too far in future") + ErrTimestampOld = errors.New("consensus: block timestamp below median") + ErrMinerTxInputs = errors.New("consensus: invalid miner transaction inputs") + ErrMinerTxHeight = errors.New("consensus: miner transaction height mismatch") + ErrMinerTxUnlock = errors.New("consensus: miner transaction unlock time invalid") + ErrRewardMismatch = errors.New("consensus: block reward mismatch") + ErrMinerTxProofs = errors.New("consensus: miner transaction proof count invalid") +) diff --git a/consensus/errors_test.go b/consensus/errors_test.go new file mode 100644 index 0000000..b298617 --- /dev/null +++ b/consensus/errors_test.go @@ -0,0 +1,16 @@ +//go:build !integration + +package consensus + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestErrors_Good(t *testing.T) { + assert.True(t, errors.Is(ErrTxTooLarge, ErrTxTooLarge)) + assert.False(t, errors.Is(ErrTxTooLarge, ErrNoInputs)) + assert.Contains(t, ErrTxTooLarge.Error(), "too large") +} diff --git a/go.mod b/go.mod index 5909416..0ba3cf7 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,21 @@ go 1.25.5 require ( forge.lthn.ai/core/go-p2p v0.0.0-00010101000000-000000000000 forge.lthn.ai/core/go-store v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.48.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/sys v0.41.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index 62528dd..9377728 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=