Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self-Contained Proofs 📦 #28

Open
wants to merge 29 commits into
base: make-permissionless
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1ec5ba1
implement SCP datastructure with tests
rmnblm Nov 30, 2018
a207a63
Merge branch 'make-permissionless' into sc-proofs
rmnblm Nov 30, 2018
dd045fb
add MerkleProof and VerifyMerkleProof functions
rmnblm Dec 3, 2018
1a1be9a
make CreateBucket public
rmnblm Dec 3, 2018
53903ff
add BoltDB as parameter to create a bucket by name
rmnblm Dec 3, 2018
3c1b28e
return error when decoding account
rmnblm Dec 3, 2018
b205fe1
implement MerkleProof datastructure into SCP
rmnblm Dec 3, 2018
95efc4e
fix MerkleProof tests :white_check_mark:
rmnblm Dec 3, 2018
40a0d7f
WIP: implement SCP miner verification
rmnblm Dec 4, 2018
886399f
WIP: extend SCP verification tests
rmnblm Dec 4, 2018
ed246d9
nil check
rmnblm Dec 4, 2018
3657069
set TotalFees in block header
rmnblm Dec 4, 2018
aab07bf
add block beneficiary to BF
rmnblm Dec 4, 2018
5ee0ecc
remove SCP data structure
rmnblm Dec 5, 2018
3f61ba7
refactor return value
rmnblm Dec 5, 2018
73dd269
add total fees validation
rmnblm Dec 5, 2018
1e9da6b
store bloom filter in local storage
rmnblm Dec 5, 2018
09d4c77
improve error messages
rmnblm Dec 5, 2018
ad5349d
continue for-loop if bloomfilter is nil
rmnblm Dec 5, 2018
a5e389a
fix failing tests :white_check_mark:
rmnblm Dec 6, 2018
5151fd2
increment TotalFees when adding a Tx to the block
rmnblm Dec 6, 2018
f18313f
WIP: let the verifyTx function return an error
rmnblm Dec 6, 2018
5bf1c0f
add TxBucket data structure
rmnblm Dec 12, 2018
4180eae
make BuildMerkleTree a struct-function of Block
rmnblm Dec 12, 2018
6dbf2da
remove newBlock function and change function signature to create a ne…
rmnblm Dec 14, 2018
ce6457e
improve TxBucket and add tests
rmnblm Dec 15, 2018
0d341b9
implement TxBuckets into rest of the code
rmnblm Dec 15, 2018
7a1d4b2
check double-spending in a single block
rmnblm Dec 15, 2018
10e3243
update verified balance if account is beneficiary
rmnblm Dec 15, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 73 additions & 57 deletions miner/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@ type blockData struct {
block *protocol.Block
}

//Block constructor, argument is the previous block in the blockchain.
func newBlock(prevHash [32]byte, commitmentProof [crypto.COMM_PROOF_LENGTH]byte, height uint32) *protocol.Block {
block := new(protocol.Block)
block.PrevHash = prevHash
block.CommitmentProof = commitmentProof
block.Height = height
block.StateCopy = make(map[[64]byte]*protocol.Account)

return block
}

//This function prepares the block to broadcast into the network. No new txs are added at this point.
func finalizeBlock(block *protocol.Block) error {
//Check if we have a slashing proof that we can add to the block.
Expand All @@ -50,8 +39,13 @@ func finalizeBlock(block *protocol.Block) error {
}
}

//Merkle tree includes the hashes of all txs.
block.MerkleRoot = protocol.BuildMerkleTree(block).MerkleRoot()
block.TxBucketData = [][32]byte{}
for _, bucket := range block.TxBuckets.Sort() {
block.TxBucketData = append(block.TxBucketData, bucket.Hash())
}

block.MerkleRoot = block.BuildMerkleTree().MerkleRoot()
block.InitBloomFilter(storage.GetTxPubKeys(block))

validatorAcc, err := storage.ReadAccount(validatorAccAddress)
if err != nil {
Expand All @@ -66,6 +60,7 @@ func finalizeBlock(block *protocol.Block) error {
if err != nil {
return err
}
copy(block.CommitmentProof[0:crypto.COMM_PROOF_LENGTH], commitmentProof[:])

partialHash := block.HashBlock()
prevProofs := GetLatestProofs(activeParameters.num_included_prev_proofs, block)
Expand All @@ -88,8 +83,7 @@ func finalizeBlock(block *protocol.Block) error {
block.NrFundsTx = uint16(len(block.FundsTxData))
block.NrConfigTx = uint8(len(block.ConfigTxData))
block.NrStakeTx = uint16(len(block.StakeTxData))

copy(block.CommitmentProof[0:crypto.COMM_PROOF_LENGTH], commitmentProof[:])
block.NrTxBucket = uint16(len(block.TxBucketData))

return nil
}
Expand All @@ -98,23 +92,14 @@ func finalizeBlock(block *protocol.Block) error {
//We do not operate global state because the work might get interrupted by receiving a block that needs validation
//which is done on the global state.
func addTx(b *protocol.Block, tx protocol.Transaction) error {
//ActiveParameters is a datastructure that stores the current system parameters, gets only changed when
//configTxs are broadcast in the network.
if tx.TxFee() < activeParameters.Fee_minimum {
logger.Printf("Transaction fee too low: %v (minimum is: %v)\n", tx.TxFee(), activeParameters.Fee_minimum)
err := fmt.Sprintf("Transaction fee too low: %v (minimum is: %v)\n", tx.TxFee(), activeParameters.Fee_minimum)
return errors.New(err)
}

//There is a trade-off what tests can be made now and which have to be delayed (when dynamic state is needed
//for inspection. The decision made is to check whether contractTx and configTx have been signed with rootAcc. This
//is a dynamic test because it needs to have access to the rootAcc state. The other option would be to include
//the address (public key of signature) in the transaction inside the tx -> would resulted in bigger tx size.
//So the trade-off is effectively clean abstraction vs. tx size. Everything related to fundsTx is postponed because
//the txs depend on each other.
if !verify(tx) {
logger.Printf("Transaction could not be verified: %v", tx)
return errors.New("Transaction could not be verified.")
if err := verify(tx); err != nil {
return errors.New(fmt.Sprintf("transaction could not be verified: %v\n", err))
}

switch tx.(type) {
Expand Down Expand Up @@ -146,6 +131,8 @@ func addTx(b *protocol.Block, tx protocol.Transaction) error {
return errors.New("Transaction type not recognized.")
}

b.TotalFees += tx.TxFee()

return nil
}

Expand Down Expand Up @@ -235,9 +222,9 @@ func addFundsTx(b *protocol.Block, tx *protocol.FundsTx) error {
accReceiver := b.StateCopy[tx.To]
accReceiver.Balance += tx.Amount

//Add the tx hash to the block header and write it to open storage (non-validated transactions).
b.FundsTxData = append(b.FundsTxData, tx.Hash())
b.AddFundsTx(tx)
logger.Printf("Added tx to the FundsTxData slice: %v", *tx)

return nil
}

Expand Down Expand Up @@ -515,7 +502,7 @@ func validate(b *protocol.Block, initialSetup bool) error {

for _, block := range blocksToValidate {
//Fetching payload data from the txs (if necessary, ask other miners).
contractTxs, fundsTxs, configTxs, stakeTxs, err := preValidate(block, initialSetup)
data, err := preValidate(block, initialSetup)

//Check if the validator that added the block has previously voted on different competing chains (find slashing proof).
//The proof will be stored in the global slashing dictionary.
Expand All @@ -527,60 +514,57 @@ func validate(b *protocol.Block, initialSetup bool) error {
return err
}

blockDataMap[block.Hash] = blockData{contractTxs, fundsTxs, configTxs, stakeTxs, block}
blockDataMap[block.Hash] = *data
if err := validateState(blockDataMap[block.Hash]); err != nil {
return err
}

postValidate(blockDataMap[block.Hash], initialSetup)
}

err = deleteZeroBalanceAccounts()
if err != nil {
return err
}
deleteZeroBalanceAccounts()

return nil
}

//Doesn't involve any state changes.
func preValidate(block *protocol.Block, initialSetup bool) (contractTxSlice []*protocol.ContractTx, fundsTxSlice []*protocol.FundsTx, configTxSlice []*protocol.ConfigTx, stakeTxSlice []*protocol.StakeTx, err error) {
func preValidate(block *protocol.Block, initialSetup bool) (data *blockData, err error) {
//This dynamic check is only done if we're up-to-date with syncing, otherwise timestamp is not checked.
//Other miners (which are up-to-date) made sure that this is correct.
if !initialSetup && uptodate {
if err := timestampCheck(block.Timestamp); err != nil {
return nil, nil, nil, nil, err
return nil, err
}
}

//Check block size.
if block.GetSize() > activeParameters.Block_size {
return nil, nil, nil, nil, errors.New("Block size too large.")
return nil, errors.New("Block size too large.")
}

//Duplicates are not allowed, use tx hash hashmap to easily check for duplicates.
duplicates := make(map[[32]byte]bool)
for _, txHash := range block.ContractTxData {
if _, exists := duplicates[txHash]; exists {
return nil, nil, nil, nil, errors.New("Duplicate Account Transaction Hash detected.")
return nil, errors.New("Duplicate Account Transaction Hash detected.")
}
duplicates[txHash] = true
}
for _, txHash := range block.FundsTxData {
if _, exists := duplicates[txHash]; exists {
return nil, nil, nil, nil, errors.New("Duplicate Funds Transaction Hash detected.")
return nil, errors.New("Duplicate Funds Transaction Hash detected.")
}
duplicates[txHash] = true
}
for _, txHash := range block.ConfigTxData {
if _, exists := duplicates[txHash]; exists {
return nil, nil, nil, nil, errors.New("Duplicate Config Transaction Hash detected.")
return nil, errors.New("Duplicate Config Transaction Hash detected.")
}
duplicates[txHash] = true
}
for _, txHash := range block.StakeTxData {
if _, exists := duplicates[txHash]; exists {
return nil, nil, nil, nil, errors.New("Duplicate Stake Transaction Hash detected.")
return nil, errors.New("Duplicate Stake Transaction Hash detected.")
}
duplicates[txHash] = true
}
Expand All @@ -589,10 +573,10 @@ func preValidate(block *protocol.Block, initialSetup bool) (contractTxSlice []*p
errChan := make(chan error, 4)

//We need to allocate slice space for the underlying array when we pass them as reference.
contractTxSlice = make([]*protocol.ContractTx, block.NrContractTx)
fundsTxSlice = make([]*protocol.FundsTx, block.NrFundsTx)
configTxSlice = make([]*protocol.ConfigTx, block.NrConfigTx)
stakeTxSlice = make([]*protocol.StakeTx, block.NrStakeTx)
contractTxSlice := make([]*protocol.ContractTx, block.NrContractTx)
fundsTxSlice := make([]*protocol.FundsTx, block.NrFundsTx)
configTxSlice := make([]*protocol.ConfigTx, block.NrConfigTx)
stakeTxSlice := make([]*protocol.StakeTx, block.NrStakeTx)

go fetchContractTxData(block, contractTxSlice, initialSetup, errChan)
go fetchFundsTxData(block, fundsTxSlice, initialSetup, errChan)
Expand All @@ -603,73 +587,105 @@ func preValidate(block *protocol.Block, initialSetup bool) (contractTxSlice []*p
for cnt := 0; cnt < 4; cnt++ {
err = <-errChan
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
}

data = &blockData{contractTxSlice, fundsTxSlice, configTxSlice, stakeTxSlice, block}

//Check state contains beneficiary.
acc, err := storage.ReadAccount(block.Beneficiary)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}

//Check if node is part of the validator set.
if !acc.IsStaking {
return nil, nil, nil, nil, errors.New(fmt.Sprintf("Validator (%x) is not part of the validator set.", acc.Address[0:8]))
return nil, errors.New(fmt.Sprintf("Validator (%x) is not part of the validator set.", acc.Address[0:8]))
}

//First, initialize an RSA Public Key instance with the modulus of the proposer of the block (acc)
//Second, check if the commitment proof of the proposed block can be verified with the public key
//Invalid if the commitment proof can not be verified with the public key of the proposer
commitmentPubKey, err := crypto.CreateRSAPubKeyFromBytes(acc.CommitmentKey)
if err != nil {
return nil, nil, nil, nil, errors.New("Invalid commitment key in account.")
return nil, errors.New("Invalid commitment key in account.")
}

err = crypto.VerifyMessageWithRSAKey(commitmentPubKey, fmt.Sprint(block.Height), block.CommitmentProof)
if err != nil {
return nil, nil, nil, nil, errors.New("The submitted commitment proof can not be verified.")
return nil, errors.New("The submitted commitment proof can not be verified.")
}

//Invalid if PoS calculation is not correct.
prevProofs := GetLatestProofs(activeParameters.num_included_prev_proofs, block)

//PoS validation
if !validateProofOfStake(getDifficulty(), prevProofs, block.Height, acc.Balance, block.CommitmentProof, block.Timestamp) {
return nil, nil, nil, nil, errors.New("The nonce is incorrect.")
return nil, errors.New("The nonce is incorrect.")
}

//Invalid if PoS is too far in the future.
now := time.Now()
if block.Timestamp > now.Unix()+int64(activeParameters.Accepted_time_diff) {
return nil, nil, nil, nil, errors.New("The timestamp is too far in the future. " + string(block.Timestamp) + " vs " + string(now.Unix()))
return nil, errors.New("The timestamp is too far in the future. " + string(block.Timestamp) + " vs " + string(now.Unix()))
}

//Check for minimum waiting time.
if block.Height-acc.StakingBlockHeight < uint32(activeParameters.Waiting_minimum) {
return nil, nil, nil, nil, errors.New("The miner must wait a minimum amount of blocks before start validating. Block Height:" + fmt.Sprint(block.Height) + " - Height when started validating " + string(acc.StakingBlockHeight) + " MinWaitingTime: " + string(activeParameters.Waiting_minimum))
return nil, errors.New("The miner must wait a minimum amount of blocks before start validating. Block Height:" + fmt.Sprint(block.Height) + " - Height when started validating " + string(acc.StakingBlockHeight) + " MinWaitingTime: " + string(activeParameters.Waiting_minimum))
}

//Check if block contains a proof for two conflicting block hashes, else no proof provided.
if block.SlashedAddress != [64]byte{} {
if _, err = slashingCheck(block.SlashedAddress, block.ConflictingBlockHash1, block.ConflictingBlockHash2); err != nil {
return nil, nil, nil, nil, err
return nil, err
}
}

// Total Fees validation
var totalFees uint64 = 0
for _, tx := range contractTxSlice {
totalFees += tx.Fee
}
for _, tx := range fundsTxSlice {
totalFees += tx.Fee
}
for _, tx := range configTxSlice {
totalFees += tx.Fee
}
for _, tx := range stakeTxSlice {
totalFees += tx.Fee
}
if totalFees != block.TotalFees {
return nil, errors.New(fmt.Sprintf("computed total fees do not equal the block's total fees %v vs. %v", totalFees, block.TotalFees))
}

/*for _, tx := range fundsTxSlice {
block.AddFundsTx(tx)
}*/

//Merkle Tree validation
if protocol.BuildMerkleTree(block).MerkleRoot() != block.MerkleRoot {
return nil, nil, nil, nil, errors.New("Merkle Root is incorrect.")
if block.BuildMerkleTree().MerkleRoot() != block.MerkleRoot {
return nil, errors.New("Merkle Root is incorrect.")
}

return contractTxSlice, fundsTxSlice, configTxSlice, stakeTxSlice, err
return data, err
}

//Dynamic state check.
//The sequence of validation matters
func validateState(data blockData) (err error) {
accStateChange(data.contractTxSlice)

// TODO @rmnblm SCP Testing Purposes
// State validation does not stop when this returns an error
// Actual funds transaction validation is below in fundsStateChange(data.fundsTxSlice)
err = verifyFundsTransactions(data.fundsTxSlice, storage.ReadAllClosedBlocks())
if err != nil {
logger.Printf("self-contained proof is invalid: %v\n", err)
}

err = fundsStateChange(data.fundsTxSlice)
if err != nil {
accStateChangeRollback(data.contractTxSlice)
Expand Down
20 changes: 11 additions & 9 deletions miner/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package miner

import (
"fmt"
"github.com/bazo-blockchain/bazo-miner/crypto"
"math/rand"
"reflect"
"testing"
Expand All @@ -18,7 +17,7 @@ import (
func TestBlock(t *testing.T) {
cleanAndPrepare()

b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1)
b := protocol.NewBlock([32]byte{}, 1)
hashFundsSlice, hashAccSlice, hashConfigSlice, hashStakeSlice := createBlockWithTxs(b)
err := finalizeBlock(b)
if err != nil {
Expand All @@ -35,7 +34,10 @@ func TestBlock(t *testing.T) {
err = validate(decodedBlock, false)

b.StateCopy = nil
b.TxBuckets = nil

decodedBlock.StateCopy = nil
decodedBlock.TxBuckets = nil

if err != nil {
t.Errorf("Block validation failed (%v)\n", err)
Expand All @@ -53,15 +55,15 @@ func TestBlock(t *testing.T) {
t.Error("StakeTx data is not properly serialized!")
}
if !reflect.DeepEqual(b, decodedBlock) {
t.Error("Either serialization or deserialization failed, blocks are not equal!")
t.Errorf("Either serialization or deserialization failed, blocks are not equal, %v vs %v", b.String(), decodedBlock.String())
}
}

//Duplicate Txs are not allowed
func TestBlockTxDuplicates(t *testing.T) {

cleanAndPrepare()
b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1)
b := protocol.NewBlock([32]byte{}, 1)
createBlockWithTxs(b)

if err := finalizeBlock(b); err != nil {
Expand Down Expand Up @@ -100,28 +102,28 @@ func TestBlockTxDuplicates(t *testing.T) {
func TestMultipleBlocks(t *testing.T) {
cleanAndPrepare()

b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1)
b := protocol.NewBlock([32]byte{}, 1)
createBlockWithTxs(b)
finalizeBlock(b)
if err := validate(b, false); err != nil {
t.Errorf("Block validation for (%v) failed: %v\n", b, err)
}

b2 := newBlock(b.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 2)
b2 := protocol.NewBlock(b.Hash, 2)
createBlockWithTxs(b2)
finalizeBlock(b2)
if err := validate(b2, false); err != nil {
t.Errorf("Block validation failed: %v\n", err)
}

b3 := newBlock(b2.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 3)
b3 := protocol.NewBlock(b2.Hash, 3)
createBlockWithTxs(b3)
finalizeBlock(b3)
if err := validate(b3, false); err != nil {
t.Errorf("Block validation failed: %v\n", err)
}

b4 := newBlock(b3.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 4)
b4 := protocol.NewBlock(b3.Hash, 4)
createBlockWithTxs(b4)
finalizeBlock(b4)
if err := validate(b4, false); err != nil {
Expand Down Expand Up @@ -165,7 +167,7 @@ func createBlockWithTxs(b *protocol.Block) ([][32]byte, [][32]byte, [][32]byte,
loopMax := int(randVar.Uint32()%testSize) + 1
loopMax += int(accA.TxCnt)
for cnt := int(accA.TxCnt); cnt < loopMax; cnt++ {
tx, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%100+1, randVar.Uint64()%100+1, uint32(cnt), accA.Address, accB.Address, PrivKeyAccA, nil)
tx, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%100+1, randVar.Uint64()%100+1, uint32(cnt), accA.Address, accB.Address, PrivKeyAccA, nil)
if err := addTx(b, tx); err == nil {
//Might be that we generated a block that was already generated before
if storage.ReadOpenTx(tx.Hash()) != nil || storage.ReadClosedTx(tx.Hash()) != nil {
Expand Down
Loading