diff --git a/miner/block.go b/miner/block.go index a4e4392..2c34992 100644 --- a/miner/block.go +++ b/miner/block.go @@ -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. @@ -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 { @@ -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) @@ -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 } @@ -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) { @@ -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 } @@ -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 } @@ -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. @@ -527,7 +514,7 @@ 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 } @@ -535,52 +522,49 @@ func validate(b *protocol.Block, initialSetup bool) error { 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 } @@ -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) @@ -603,19 +587,21 @@ 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) @@ -623,12 +609,12 @@ func preValidate(block *protocol.Block, initialSetup bool) (contractTxSlice []*p //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. @@ -636,33 +622,55 @@ func preValidate(block *protocol.Block, initialSetup bool) (contractTxSlice []*p //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. @@ -670,6 +678,14 @@ func preValidate(block *protocol.Block, initialSetup bool) (contractTxSlice []*p 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) diff --git a/miner/block_test.go b/miner/block_test.go index 1200b2f..e12d66e 100644 --- a/miner/block_test.go +++ b/miner/block_test.go @@ -2,7 +2,6 @@ package miner import ( "fmt" - "github.com/bazo-blockchain/bazo-miner/crypto" "math/rand" "reflect" "testing" @@ -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 { @@ -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) @@ -53,7 +55,7 @@ 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()) } } @@ -61,7 +63,7 @@ func TestBlock(t *testing.T) { 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 { @@ -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 { @@ -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 { diff --git a/miner/blockchain.go b/miner/blockchain.go index bb09d2f..3cd0a0f 100644 --- a/miner/blockchain.go +++ b/miner/blockchain.go @@ -64,7 +64,7 @@ func InitFirstStart(wallet *ecdsa.PublicKey, commitment *rsa.PrivateKey) error { //Mining is a constant process, trying to come up with a successful PoW. func mining(initialBlock *protocol.Block) { - currentBlock := newBlock(initialBlock.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, initialBlock.Height+1) + currentBlock := protocol.NewBlock(initialBlock.Hash, initialBlock.Height+1) for { _, err := storage.ReadAccount(validatorAccAddress) @@ -96,7 +96,7 @@ func mining(initialBlock *protocol.Block) { //validated with block validation, so we wait in order to not work on tx data that is already validated //when we finish the block. blockValidation.Lock() - nextBlock := newBlock(lastBlock.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, lastBlock.Height+1) + nextBlock := protocol.NewBlock(lastBlock.Hash, lastBlock.Height+1) currentBlock = nextBlock prepareBlock(currentBlock) blockValidation.Unlock() diff --git a/miner/blockchainparam_test.go b/miner/blockchainparam_test.go index d7d71e7..bd4f60c 100644 --- a/miner/blockchainparam_test.go +++ b/miner/blockchainparam_test.go @@ -1,7 +1,6 @@ package miner import ( - "github.com/bazo-blockchain/bazo-miner/crypto" "github.com/bazo-blockchain/bazo-miner/protocol" "testing" ) @@ -19,7 +18,7 @@ func TestTargetHistory(t *testing.T) { var tmpBlock *protocol.Block tmpBlock = new(protocol.Block) for cnt := 0; cnt < 10; cnt++ { - tmpBlock = newBlock(tmpBlock.Hash, [crypto.COMM_KEY_LENGTH]byte{}, tmpBlock.Height+1) + tmpBlock = protocol.NewBlock(tmpBlock.Hash, tmpBlock.Height+1) finalizeBlock(tmpBlock) validate(tmpBlock, false) blocks = append(blocks, tmpBlock) @@ -53,7 +52,7 @@ func TestTargetHistory(t *testing.T) { targetSize = len(target) targetTimesSize = len(targetTimes) - tmpBlock = newBlock(blocks[len(blocks)-1].Hash, [crypto.COMM_PROOF_LENGTH]byte{}, blocks[len(blocks)-1].Height+1) + tmpBlock = protocol.NewBlock(blocks[len(blocks)-1].Hash, blocks[len(blocks)-1].Height+1) finalizeBlock(tmpBlock) validate(tmpBlock, false) @@ -72,7 +71,7 @@ func TestTimestamps(t *testing.T) { prevHash := [32]byte{} for cnt := 0; cnt < 0; cnt++ { - b := newBlock(prevHash, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock(prevHash, 1) if cnt == 8 { tx, err := protocol.ConstrConfigTx(0, protocol.DIFF_INTERVAL_ID, 20, 2, 0, PrivKeyRoot) diff --git a/miner/blockpreparation_test.go b/miner/blockpreparation_test.go index 7a98174..3e58c3e 100644 --- a/miner/blockpreparation_test.go +++ b/miner/blockpreparation_test.go @@ -1,7 +1,6 @@ package miner import ( - "github.com/bazo-blockchain/bazo-miner/crypto" "math/rand" "testing" "time" @@ -17,14 +16,14 @@ func TestPrepareAndSortTxs(t *testing.T) { //fill the open storage with fundstx randVar := rand.New(rand.NewSource(time.Now().Unix())) for cnt := 0; cnt < testsize; cnt++ { - tx, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%100+1, randVar.Uint64()%100+1, uint32(cnt), accA.Address, accB.Address, PrivKeyAccA, nil) - tx2, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%100+1, randVar.Uint64()%100+1, uint32(cnt), accB.Address, accA.Address, PrivKeyAccB, nil) + tx, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%100+1, randVar.Uint64()%100+1, uint32(cnt), accA.Address, accB.Address, PrivKeyAccA, nil) + tx2, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%100+1, randVar.Uint64()%100+1, uint32(cnt), accB.Address, accA.Address, PrivKeyAccB, nil) - if verifyFundsTx(tx) { + if verifyFundsTx(tx) == nil { storage.WriteOpenTx(tx) } - if verifyFundsTx(tx2) { + if verifyFundsTx(tx2) == nil{ storage.WriteOpenTx(tx2) } } @@ -32,7 +31,7 @@ func TestPrepareAndSortTxs(t *testing.T) { //Add other tx types as well to make the test more challenging for cnt := 0; cnt < testsize; cnt++ { tx, _, _ := protocol.ConstrContractTx(0x01, randVar.Uint64()%100+1, PrivKeyRoot, nil, nil) - if verifyContractTx(tx) { + if verifyContractTx(tx) == nil { storage.WriteOpenTx(tx) } } @@ -44,12 +43,12 @@ func TestPrepareAndSortTxs(t *testing.T) { if tx.Id == 3 || tx.Id == 1 { continue } - if verifyConfigTx(tx) { + if verifyConfigTx(tx) == nil { storage.WriteOpenTx(tx) } } - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) prepareBlock(b) finalizeBlock(b) diff --git a/miner/blockrollback_test.go b/miner/blockrollback_test.go index c004b5e..be76d01 100644 --- a/miner/blockrollback_test.go +++ b/miner/blockrollback_test.go @@ -1,7 +1,6 @@ package miner import ( - "github.com/bazo-blockchain/bazo-miner/crypto" "reflect" "testing" @@ -13,7 +12,7 @@ import ( func TestValidateBlockRollback(t *testing.T) { cleanAndPrepare() - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) //Make state snapshot accsBefore := make(map[[64]byte]protocol.Account) @@ -72,7 +71,7 @@ func TestMultipleBlocksRollback(t *testing.T) { var paramb2 []Parameters var paramb3 []Parameters - 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 { @@ -86,7 +85,7 @@ func TestMultipleBlocksRollback(t *testing.T) { paramb = make([]Parameters, len(parameterSlice)) copy(paramb, parameterSlice) - 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 { @@ -100,7 +99,7 @@ func TestMultipleBlocksRollback(t *testing.T) { paramb2 = make([]Parameters, len(parameterSlice)) copy(paramb2, parameterSlice) - 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 { @@ -114,7 +113,7 @@ func TestMultipleBlocksRollback(t *testing.T) { paramb3 = make([]Parameters, len(parameterSlice)) copy(paramb3, parameterSlice) - 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 { diff --git a/miner/contract_test.go b/miner/contract_test.go index bec3c68..80bb1e8 100644 --- a/miner/contract_test.go +++ b/miner/contract_test.go @@ -16,7 +16,7 @@ import ( func TestMultipleBlocksWithContractTx(t *testing.T) { cleanAndPrepare() - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) contract := []byte{ 35, // CALLDATA 0, 1, 0, 5, // PUSH 5 @@ -29,7 +29,7 @@ func TestMultipleBlocksWithContractTx(t *testing.T) { 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) transactionData := []byte{ 1, 0, 15, } @@ -45,7 +45,7 @@ func TestMultipleBlocksWithContractTx(t *testing.T) { func TestMultipleBlocksWithStateChangeContractTx(t *testing.T) { cleanAndPrepare() - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) contract := []byte{ 35, // CALLDATA 29, 0, // SLOAD @@ -59,7 +59,7 @@ func TestMultipleBlocksWithStateChangeContractTx(t *testing.T) { 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) transactionData := []byte{ 1, 0, 15, } @@ -82,7 +82,7 @@ func TestMultipleBlocksWithStateChangeContractTx(t *testing.T) { func TestMultipleBlocksWithDoubleStateChangeContractTx(t *testing.T) { cleanAndPrepare() - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) contract := []byte{ 35, // CALLDATA 29, 0, // SLOAD @@ -96,7 +96,7 @@ func TestMultipleBlocksWithDoubleStateChangeContractTx(t *testing.T) { 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) transactionData := []byte{ 1, 0, 15, } @@ -106,7 +106,7 @@ func TestMultipleBlocksWithDoubleStateChangeContractTx(t *testing.T) { t.Errorf("Block validation failed: %v\n", err) } - b3 := newBlock(b2.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 3) + b3 := protocol.NewBlock(b2.Hash, 3) transactionData = []byte{ 1, 0, 15, } @@ -127,7 +127,7 @@ func TestMultipleBlocksWithDoubleStateChangeContractTx(t *testing.T) { func TestMultipleBlocksWithContextContractTx(t *testing.T) { cleanAndPrepare() - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) contract := []byte{ 35, 0, 0, 1, 10, 22, 0, 10, 1, 50, 28, 0, 31, 33, 10, 22, 0, 21, 2, 24, 28, 0, 29, 0, 0, 4, 27, 0, 0, 24, } @@ -137,9 +137,9 @@ func TestMultipleBlocksWithContextContractTx(t *testing.T) { t.Errorf("Block validation for (%v) failed: %v\n", b, err) } - b1 := newBlock(b.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 2) + b1 := protocol.NewBlock(b.Hash, 2) transactionData := []byte{ - 0, 100, // Amount + 0, 100, // BucketRelativeBalance 0, 1, } createBlockWithSingleContractCallTx(contractAddress, b1, transactionData) @@ -153,7 +153,7 @@ func TestMultipleBlocksWithContextContractTx(t *testing.T) { func TestMultipleBlocksWithTokenizationContractTx(t *testing.T) { cleanAndPrepare() - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) contract := []byte{ 35, 1, 0, 0, 1, 10, 22, 0, 11, 3, 50, 28, 1, 28, 0, 29, 1, 33, 10, 22, 0, 24, 2, 24, 28, 1, 28, 0, 1, 29, 2, 37, 22, 0, 46, 2, 28, 1, 28, 0, 29, 2, 38, 27, 2, 50, 28, 1, 29, 2, 39, 28, 0, 4, 28, 1, 29, 2, 40, 27, 2, 50, } @@ -175,9 +175,9 @@ func TestMultipleBlocksWithTokenizationContractTx(t *testing.T) { t.Errorf("Block validation for (%v) failed: %v\n", b, err) } - b1 := newBlock(b.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 2) + b1 := protocol.NewBlock(b.Hash, 2) transactionData := []byte{ - 1, 0, 100, // Amount + 1, 0, 100, // BucketRelativeBalance 1, receiver[0], receiver[1], // receiver address 1, 0, 1, // function Hash } @@ -209,7 +209,7 @@ func TestMultipleBlocksWithTokenizationContractTx(t *testing.T) { func TestMultipleBlocksWithTokenizationContractTxWhichAddsKey(t *testing.T) { cleanAndPrepare() - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) contract := []byte{ 35, 1, 0, 0, 1, 10, 22, 0, 11, 3, 50, 28, 1, 28, 0, 29, 1, 33, 10, 22, 0, 24, 2, 24, 28, 1, 28, 0, 1, 29, 2, 37, 22, 0, 46, 2, 28, 1, 28, 0, 29, 2, 38, 27, 2, 50, 28, 1, 29, 2, 39, 28, 0, 4, 28, 1, 29, 2, 40, 27, 2, 50, } @@ -231,9 +231,9 @@ func TestMultipleBlocksWithTokenizationContractTxWhichAddsKey(t *testing.T) { t.Errorf("Block validation for (%v) failed: %v\n", b, err) } - b1 := newBlock(b.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 2) + b1 := protocol.NewBlock(b.Hash, 2) transactionData := []byte{ - 1, 0, 100, // Amount + 1, 0, 100, // BucketRelativeBalance 1, receiver[0], receiver[1], // receiver address 1, 0, 1, // function Hash } @@ -273,7 +273,7 @@ func createBlockWithSingleContractDeployTx(b *protocol.Block, contract []byte, c } func createBlockWithSingleContractCallTx(contractAddress [64]byte, b *protocol.Block, transactionData []byte) { - tx, _ := protocol.ConstrFundsTx(0x01, rand.Uint64()%100+1, 100000, uint32(accA.TxCnt), accA.Address, contractAddress, PrivKeyAccA, transactionData) + tx, _ := protocol.NewSignedFundsTx(0x01, rand.Uint64()%100+1, 100000, uint32(accA.TxCnt), accA.Address, contractAddress, PrivKeyAccA, transactionData) if err := addTx(b, tx); err == nil { storage.WriteOpenTx(tx) } else { @@ -285,7 +285,7 @@ func createBlockWithSingleContractCallTxDefined(b *protocol.Block, transactionDa accA, _ := storage.ReadAccount(from) accB, _ := storage.ReadAccount(to) - tx, _ := protocol.ConstrFundsTx(0x01, rand.Uint64()%100+1, rand.Uint64()%100+1, uint32(accA.TxCnt), accA.Address, accB.Address, PrivKeyAccA, transactionData) + tx, _ := protocol.NewSignedFundsTx(0x01, rand.Uint64()%100+1, rand.Uint64()%100+1, uint32(accA.TxCnt), accA.Address, accB.Address, PrivKeyAccA, transactionData) if err := addTx(b, tx); err == nil { storage.WriteOpenTx(tx) } else { diff --git a/miner/longestchain_test.go b/miner/longestchain_test.go index 9454251..6c129bc 100644 --- a/miner/longestchain_test.go +++ b/miner/longestchain_test.go @@ -1,7 +1,7 @@ package miner import ( - "github.com/bazo-blockchain/bazo-miner/crypto" + "github.com/bazo-blockchain/bazo-miner/protocol" "github.com/bazo-blockchain/bazo-miner/storage" "testing" ) @@ -12,17 +12,17 @@ func TestGetBlockSequences(t *testing.T) { cleanAndPrepare() - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) createBlockWithTxs(b) finalizeBlock(b) validate(b, false) - b2 := newBlock(b.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, b.Height+1) + b2 := protocol.NewBlock(b.Hash, b.Height+1) createBlockWithTxs(b2) finalizeBlock(b2) validate(b2, false) - b3 := newBlock(b2.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, b2.Height+1) + b3 := protocol.NewBlock(b2.Hash, b2.Height+1) createBlockWithTxs(b3) if err := finalizeBlock(b3); err != nil { t.Error(err) @@ -41,7 +41,7 @@ func TestGetBlockSequences(t *testing.T) { //PoW needs lastBlock, have to set it manually lastBlock = storage.ReadClosedBlock([32]byte{}) - c := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + c := protocol.NewBlock([32]byte{}, 1) createBlockWithTxs(c) if err := finalizeBlock(c); err != nil { t.Error(err) @@ -51,7 +51,7 @@ func TestGetBlockSequences(t *testing.T) { //PoW needs lastBlock, have to set it manually lastBlock = c - c2 := newBlock(c.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, c.Height+1) + c2 := protocol.NewBlock(c.Hash, c.Height+1) createBlockWithTxs(c2) if err := finalizeBlock(c2); err != nil { t.Error(err) @@ -61,7 +61,7 @@ func TestGetBlockSequences(t *testing.T) { //PoW needs lastBlock, have to set it manually lastBlock = c2 - c3 := newBlock(c2.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, c.Height+1) + c3 := protocol.NewBlock(c2.Hash, c.Height+1) createBlockWithTxs(c3) finalizeBlock(c3) @@ -86,17 +86,17 @@ func TestGetBlockSequences(t *testing.T) { cleanAndPrepare() //Make sure that another chain of equal length does not get activated - b = newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b = protocol.NewBlock([32]byte{}, 1) createBlockWithTxs(b) finalizeBlock(b) validate(b, false) - b2 = newBlock(b.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, b.Height+1) + b2 = protocol.NewBlock(b.Hash, b.Height+1) createBlockWithTxs(b2) finalizeBlock(b2) validate(b2, false) - b3 = newBlock(b2.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, b2.Height+1) + b3 = protocol.NewBlock(b2.Hash, b2.Height+1) createBlockWithTxs(b3) finalizeBlock(b3) validate(b3, false) @@ -104,19 +104,19 @@ func TestGetBlockSequences(t *testing.T) { //Blockchain now: genesis <- b <- b2 <- b3 //Competing chain: genesis <- c <- c2 <- c3 lastBlock = storage.ReadClosedBlock([32]byte{}) - c = newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + c = protocol.NewBlock([32]byte{}, 1) createBlockWithTxs(c) finalizeBlock(c) storage.WriteOpenBlock(c) lastBlock = c - c2 = newBlock(c.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, c.Height+1) + c2 = protocol.NewBlock(c.Hash, c.Height+1) createBlockWithTxs(c2) finalizeBlock(c2) storage.WriteOpenBlock(c2) lastBlock = c2 - c3 = newBlock(c2.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, c2.Height+1) + c3 = protocol.NewBlock(c2.Hash, c2.Height+1) createBlockWithTxs(c3) finalizeBlock(c3) @@ -133,12 +133,12 @@ func TestGetBlockSequences(t *testing.T) { func TestGetNewChain(t *testing.T) { cleanAndPrepare() - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) createBlockWithTxs(b) finalizeBlock(b) validate(b, false) - b2 := newBlock(b.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, b.Height+1) + b2 := protocol.NewBlock(b.Hash, b.Height+1) createBlockWithTxs(b2) finalizeBlock(b2) @@ -154,13 +154,13 @@ func TestGetNewChain(t *testing.T) { //Blockchain now: genesis <- b //New chain: genesis <- c <- c2 lastBlock = storage.ReadClosedBlock([32]byte{}) - c := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + c := protocol.NewBlock([32]byte{}, 1) createBlockWithTxs(c) finalizeBlock(c) storage.WriteOpenBlock(c) lastBlock = c - c2 := newBlock(c.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, c.Height+1) + c2 := protocol.NewBlock(c.Hash, c.Height+1) createBlockWithTxs(c2) finalizeBlock(c2) diff --git a/miner/main_test.go b/miner/main_test.go index 9c25116..2716098 100644 --- a/miner/main_test.go +++ b/miner/main_test.go @@ -202,7 +202,8 @@ func cleanAndPrepare() { crypto.GetBytesFromRSAPubKey(&CommPrivKeyRoot.PublicKey)) commitmentProof, _ := crypto.SignMessageWithRSAKey(CommPrivKeyRoot, "0") - initialBlock = newBlock(genesis.Hash(), commitmentProof, 0) + initialBlock = protocol.NewBlock(genesis.Hash(), 0) + initialBlock.CommitmentProof = commitmentProof collectStatistics(initialBlock) if err := storage.WriteClosedBlock(initialBlock); err != nil { diff --git a/miner/p2p_interface.go b/miner/p2p_interface.go index 22db876..b2a426a 100644 --- a/miner/p2p_interface.go +++ b/miner/p2p_interface.go @@ -39,12 +39,7 @@ func processBlock(payload []byte) { //p2p.BlockOut is a channel whose data get consumed by the p2p package func broadcastBlock(block *protocol.Block) { p2p.BlockOut <- block.Encode() - - //Make a deep copy of the block (since it is a pointer and will be saved to db later). - //Otherwise the block's bloom filter is initialized on the original block. - var blockCopy = *block - blockCopy.InitBloomFilter(append(storage.GetTxPubKeys(&blockCopy))) - p2p.BlockHeaderOut <- blockCopy.EncodeHeader() + p2p.BlockHeaderOut <- block.EncodeHeader() } func broadcastVerifiedTxs(txs []*protocol.FundsTx) { diff --git a/miner/proofofstake_test.go b/miner/proofofstake_test.go index 05e2e12..3046d90 100644 --- a/miner/proofofstake_test.go +++ b/miner/proofofstake_test.go @@ -3,6 +3,7 @@ package miner import ( "fmt" "github.com/bazo-blockchain/bazo-miner/crypto" + "github.com/bazo-blockchain/bazo-miner/protocol" "math/rand" "reflect" "testing" @@ -45,7 +46,7 @@ func TestGetLatestProofs(t *testing.T) { proofs = append([][crypto.COMM_PROOF_LENGTH]byte{genesisCommitmentProof}, proofs...) //Initially we expect only the genesis commitment proof - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) prevProofs := GetLatestProofs(1, b) @@ -57,21 +58,21 @@ func TestGetLatestProofs(t *testing.T) { } //Two new blocks are added with random commitment proofs - b1 := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b1 := protocol.NewBlock([32]byte{}, 1) if err := finalizeBlock(b1); err != nil { t.Error("Error finalizing b1", err) } proofs = append([][crypto.COMM_PROOF_LENGTH]byte{b1.CommitmentProof}, proofs...) validate(b1, false) - b2 := newBlock(b1.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, b1.Height+1) + b2 := protocol.NewBlock(b1.Hash, b1.Height+1) if err := finalizeBlock(b2); err != nil { t.Error("Error finalizing b2", err) } validate(b2, false) proofs = append([][crypto.COMM_PROOF_LENGTH]byte{b2.CommitmentProof}, proofs...) - b3 := newBlock(b2.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, b2.Height+1) + b3 := protocol.NewBlock(b2.Hash, b2.Height+1) prevProofs = GetLatestProofs(3, b3) diff --git a/miner/slashing_test.go b/miner/slashing_test.go index 79c12ad..a9b9b80 100644 --- a/miner/slashing_test.go +++ b/miner/slashing_test.go @@ -1,7 +1,7 @@ package miner import ( - "github.com/bazo-blockchain/bazo-miner/crypto" + "github.com/bazo-blockchain/bazo-miner/protocol" "github.com/bazo-blockchain/bazo-miner/storage" "reflect" "testing" @@ -13,7 +13,7 @@ func TestSlashingCondition(t *testing.T) { myAcc, _ := storage.ReadAccount(validatorAccAddress) initBalance := myAcc.Balance - forkBlock := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + forkBlock := protocol.NewBlock([32]byte{}, 1) if err := finalizeBlock(forkBlock); err != nil { t.Errorf("Block finalization for b1 (%v) failed: %v\n", forkBlock, err) } @@ -22,7 +22,7 @@ func TestSlashingCondition(t *testing.T) { } // genesis <- forkBlock <- b - b := newBlock(forkBlock.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 2) + b := protocol.NewBlock(forkBlock.Hash, 2) if err := finalizeBlock(b); err != nil { t.Errorf("Block finalization for b1 (%v) failed: %v\n", b, err) } @@ -34,7 +34,7 @@ func TestSlashingCondition(t *testing.T) { lastBlock = forkBlock // genesis <- forkBlock <- b2 - b2 := newBlock(forkBlock.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 2) + b2 := protocol.NewBlock(forkBlock.Hash, 2) if err := finalizeBlock(b2); err != nil { t.Errorf("Block finalization for b2 (%v) failed: %v\n", b2, err) } @@ -52,7 +52,7 @@ func TestSlashingCondition(t *testing.T) { } //third block contains the slashing proof - b3 := newBlock(b2.Hash, [crypto.COMM_PROOF_LENGTH]byte{}, 3) + b3 := protocol.NewBlock(b2.Hash, 3) if err := finalizeBlock(b3); err != nil { t.Errorf("Block finalization for b3 (%v) failed: %v\n", b3, err) } diff --git a/miner/state.go b/miner/state.go index 96f8fbe..c3cfde7 100644 --- a/miner/state.go +++ b/miner/state.go @@ -202,7 +202,7 @@ func getInitialBlock(genesis *protocol.Genesis) (initialBlock *protocol.Block, e //Set the last closed block as the initial block initialBlock = storage.AllClosedBlocksAsc[len(storage.AllClosedBlocksAsc)-1] } else { - initialBlock = newBlock(genesis.Hash(), [crypto.COMM_PROOF_LENGTH]byte{}, 0) + initialBlock = protocol.NewBlock(genesis.Hash(), 0) commitmentProof, err := crypto.SignMessageWithRSAKey(commPrivKey, fmt.Sprint(initialBlock.Height)) if err != nil { @@ -229,12 +229,12 @@ func validateClosedBlocks() error { //Do not validate the genesis block, since a lot of properties are set to nil if blockToValidate.Hash != [32]byte{} { //Fetching payload data from the txs (if necessary, ask other miners) - contractTxs, fundsTxs, configTxs, stakeTxs, err := preValidate(blockToValidate, true) + data, err := preValidate(blockToValidate, true) if err != nil { return errors.New(fmt.Sprintf("Block (%x) could not be prevalidated: %v\n", blockToValidate.Hash[0:8], err)) } - blockDataMap[blockToValidate.Hash] = blockData{contractTxs, fundsTxs, configTxs, stakeTxs, blockToValidate} + blockDataMap[blockToValidate.Hash] = *data err = validateState(blockDataMap[blockToValidate.Hash]) if err != nil { @@ -339,6 +339,121 @@ func fundsStateChange(txSlice []*protocol.FundsTx) (err error) { return nil } +func verifyFundsTransactions(txSlice []*protocol.FundsTx, previousBlocks []*protocol.Block) (err error) { + tmpBuckets := make(map[protocol.AddressType]*protocol.TxBucket) + + for _, tx := range txSlice { + var rootAcc *protocol.Account + //Check if we have to issue new coins (in case a root account signed the tx) + if rootAcc, err = storage.ReadRootAccount(tx.From); err != nil { + return err + } + + if rootAcc != nil && rootAcc.Balance+tx.Amount+tx.Fee > MAX_MONEY { + return errors.New("transaction amount would lead to balance overflow at the receiver (root) account") + } + + // Only verify SCP if sender is no root + if rootAcc != nil { + continue + } + + tmpBucket, exists := tmpBuckets[tx.From] + if !exists { + tmpBucket = protocol.NewTxBucket(tx.From) + tmpBuckets[tx.From] = tmpBucket + } + tmpBucket.AddFundsTx(tx) + } + + for _, bucket := range tmpBuckets { + tx := bucket.Transactions[0] // TODO @rmnblm we only check the first transaction to get the verified balance + verifiedBalance, err := verifySCP(tx, previousBlocks) + if err != nil { + return err + } + + if verifiedBalance + bucket.RelativeBalance < 0 { + return errors.New(fmt.Sprintf("verifying funds transactions failed: Address %x " + + "wants to spend more than actually available, (verified %v, relative %v)", + bucket.Address[0:8], verifiedBalance, bucket.RelativeBalance)) + } + + logger.Printf("self-contained proof is valid, yay!") + } + + return nil +} + +func verifySCP(tx *protocol.FundsTx, previousBlocks []*protocol.Block) (verifiedBalance int64, err error) { + proofIndex := 0 + sender := tx.From[:] + + for _, currentBlock := range previousBlocks { + if currentBlock.Beneficiary == tx.From { + verifiedBalance += int64(currentBlock.TotalFees) + } + + // Bloom filters never give false-negative, so if it does not contain the sender, + // we can easily skip the current block + if currentBlock.BloomFilter == nil || !currentBlock.BloomFilter.Test(sender) { + continue + } + + if proofIndex >= len(tx.Proofs) { + return 0, errors.New(fmt.Sprintf("Bloom filter returned true but Merkle proof missing for block at height %v", currentBlock.Height)) + } + + currentProof := tx.Proofs[proofIndex] + // There must be at least one proof for the current block because the BF returned true + if currentProof.Height < currentBlock.Height { + return 0, errors.New(fmt.Sprintf("SCP does not contain a prof for block at height %v", currentBlock.Height)) + } + + for { + // Compare the current proof (CP) height with the current block (CB) height + // CP.Height < CB.Height -> The current proof is for an earlier block + // CP.Height = CB.Height -> The current proof is for the current block + // CP.Height > CB.Height -> The current proof is for a later block (should not happen, SCP is out of order) + if currentProof.Height < currentBlock.Height { + // Get out of the infinite loop + break + } else if currentProof.Height > currentBlock.Height { + return 0, errors.New(fmt.Sprintf("SCP is out of order because height of proof (%v) is greater than height of current block (%v)", currentProof.Height, currentBlock.Height)) + } + + merkleRoot, err := currentProof.CalculateMerkleRoot() + if err != nil { + return 0, err + } + + if currentBlock.MerkleRoot != merkleRoot { + return 0, errors.New(fmt.Sprintf("Merkle root does not match %x vs. %x", currentBlock.MerkleRoot, merkleRoot)) + } + + if currentProof.BucketRelativeBalance == 0 { + // False-Positive Proof + // TODO @mrmnblm + } else if currentProof.BucketAddress == tx.From || currentProof.BucketAddress == tx.To { + // Note that currentProof.BucketRelativeBalance can be either positive or negative + verifiedBalance += currentProof.BucketRelativeBalance + } + + proofIndex++ + if proofIndex >= len(tx.Proofs) { + break + } + currentProof = tx.Proofs[proofIndex] + } + } + + if verifiedBalance < int64(tx.Amount) { + return 0, errors.New(fmt.Sprintf("verified balance less than amount (%v < %v) spent by acc %x", verifiedBalance, tx.Amount, tx.From[0:8])) + } + + return verifiedBalance, nil +} + //We accept config slices with unknown id, but don't act on the payload. This is in case we have not updated to a new //software with corresponding code to act on the configTx id/payload func configStateChange(configTxSlice []*protocol.ConfigTx, blockHash [32]byte) { @@ -533,7 +648,7 @@ func updateStakingHeight(block *protocol.Block) error { return nil } -func deleteZeroBalanceAccounts() error { +func deleteZeroBalanceAccounts() { for _, acc := range storage.State { if acc.Balance > 0 || acc.Contract != nil { continue @@ -541,6 +656,4 @@ func deleteZeroBalanceAccounts() error { storage.DeleteAccount(acc.Address) } - - return nil } diff --git a/miner/state_test.go b/miner/state_test.go index cd69537..d96fc7e 100644 --- a/miner/state_test.go +++ b/miner/state_test.go @@ -20,7 +20,7 @@ func TestFundsTxStateChange(t *testing.T) { var testSize uint32 testSize = 1000 - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) var funds []*protocol.FundsTx var feeA, feeB uint64 @@ -34,7 +34,7 @@ func TestFundsTxStateChange(t *testing.T) { loopMax := int(randVar.Uint32()%testSize + 1) for i := 0; i < loopMax+1; i++ { - ftx, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%1000000+1, randVar.Uint64()%100+1, uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) + ftx, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%1000000+1, randVar.Uint64()%100+1, uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) if addTx(b, ftx) == nil { funds = append(funds, ftx) balanceA -= ftx.Amount @@ -43,7 +43,7 @@ func TestFundsTxStateChange(t *testing.T) { balanceB += ftx.Amount } - ftx2, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%1000+1, randVar.Uint64()%100+1, uint32(i), accA.Address, accB.Address, PrivKeyAccB, nil) + ftx2, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%1000+1, randVar.Uint64()%100+1, uint32(i), accA.Address, accB.Address, PrivKeyAccB, nil) if addTx(b, ftx2) == nil { funds = append(funds, ftx2) balanceB -= ftx2.Amount @@ -79,11 +79,17 @@ func TestAccountOverflow(t *testing.T) { accA.Balance = MAX_MONEY accA.TxCnt = 0 - tx, err := protocol.ConstrFundsTx(0x01, 1, 1, 0, accB.Address, accA.Address, PrivKeyAccB, nil) - if !verifyFundsTx(tx) || err != nil { - t.Error("Failed to create reasonable fundsTx\n") + tx, err := protocol.NewSignedFundsTx(0x01, 1, 1, 0, accB.Address, accA.Address, PrivKeyAccB, nil) + if err != nil { + t.Error(err) + return + } + + if err = verifyFundsTx(tx); err != nil { + t.Error(err) return } + accSlice = append(accSlice, tx) err = fundsStateChange(accSlice) @@ -182,7 +188,7 @@ func TestFundsTxNewAccsStateChange(t *testing.T) { rand.Read(fromAddress[:]) rand.Read(toAddress[:]) - tx, _ := protocol.ConstrFundsTx(0, 0, 0, 0, fromAddress, toAddress, PrivKeyRoot, nil) + tx, _ := protocol.NewSignedFundsTx(0, 0, 0, 0, fromAddress, toAddress, PrivKeyRoot, nil) fundsTxs = append(fundsTxs, tx) } @@ -219,7 +225,7 @@ func TestConfigTxStateChange(t *testing.T) { if err != nil { t.Errorf("ConfigTx Creation failed (%v)\n", err) } - if verifyConfigTx(tx) { + if verifyConfigTx(tx) == nil { configs = append(configs, tx) } } @@ -331,7 +337,7 @@ func TestStakeTxStateChange(t *testing.T) { randVar := rand.New(rand.NewSource(time.Now().Unix())) - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) var stake, stake2 []*protocol.StakeTx accA.IsStaking = false @@ -360,3 +366,201 @@ func TestStakeTxStateChange(t *testing.T) { } } + +// Test veirifySCP with one transaction per block (sufficient funds) +func TestVerifySCP1TS(t *testing.T) { + cleanAndPrepare() + + // Setup test by transferring 100 coins to account B + b := protocol.NewBlock([32]byte{}, 0) + tx, _ := protocol.NewSignedFundsTx(0x01, 100, 1, uint32(0), accA.Address, accB.Address, PrivKeyAccA, nil) + if err := addTx(b, tx); err != nil { + t.Error(err) + } + + // Get the transaction bucket of account B and check if it exists + bucket, exists := b.TxBuckets[accB.Address] + if !exists { + t.Errorf("transaction bucket is not part of the block for address %x", accA.Address[0:8]) + } + + // Finalize the first block + storage.WriteOpenTx(tx) + finalizeBlock(b) + + // Get the Merkle proof for the transcaction bucket of account B + mhashes, err := b.BuildMerkleTree().MerkleProof(bucket.Hash()) + if err != nil { + t.Error(err) + } + // Create a new Merkle proof for the transaction bucket + merkleProof := protocol.NewMerkleProof(b.Height, mhashes, bucket.Address, bucket.RelativeBalance, bucket.CalculateMerkleRoot()) + + // Create a new transaction that contains the previous SCP + tx1, _ := protocol.NewSignedFundsTx(0x01, 50, 1, uint32(0), accB.Address, accA.Address, PrivKeyAccB, nil) + tx1.Proofs = append(tx1.Proofs, &merkleProof) + // Verify if the SCP is valid + if err := verifyFundsTransactions([]*protocol.FundsTx{tx1}, []*protocol.Block{b}); err != nil { + t.Error(err) + } + + // Repeat previous step: Create another new block + b1 := protocol.NewBlock(b.Hash, 1) + if err := addTx(b1, tx1); err != nil { + t.Error(err) + } + + bucket, exists = b1.TxBuckets[accB.Address] + if !exists { + t.Errorf("transaction bucket is not part of the block for address %x", accB.Address[0:8]) + } + + storage.WriteOpenTx(tx1) + finalizeBlock(b1) + + mhashes, err = b1.BuildMerkleTree().MerkleProof(bucket.Hash()) + if err != nil { + t.Error(err) + } + merkleProof1 := protocol.NewMerkleProof(b1.Height, mhashes, bucket.Address, bucket.RelativeBalance, bucket.CalculateMerkleRoot()) + + // Create another transaction that contains SCP + tx2, _ := protocol.NewSignedFundsTx(0x01, 50, 1, uint32(0), accB.Address, accA.Address, PrivKeyAccB, nil) + tx2.Proofs = append(tx2.Proofs, &merkleProof1, &merkleProof) + if err := verifyFundsTransactions([]*protocol.FundsTx{tx2}, []*protocol.Block{b1, b}); err == nil { + t.Error("self-contained proof should be invalid (insufficient funds) but verifySCP returns no error") + } +} + +// Test veirifySCP with a block that contains two transactions in one block (sufficient funds) +func TestVerifySCP2TS(t *testing.T) { + cleanAndPrepare() + + // Setup test by transferring 100 coins to account B + b := protocol.NewBlock([32]byte{}, 0) + tx, _ := protocol.NewSignedFundsTx(0x01, 100, 1, uint32(0), accA.Address, accB.Address, PrivKeyAccA, nil) + + // Current Balance of Account B: 100 + + if err := addTx(b, tx); err != nil { + t.Error(err) + } + + // Get the transaction bucket of account B and check if it exists + bucket, exists := b.TxBuckets[accB.Address] + if !exists { + t.Errorf("transaction bucket is not part of the block for address %x", accA.Address[0:8]) + } + + // Finalize the first block + storage.WriteOpenTx(tx) + finalizeBlock(b) + + // Get the Merkle proof for the transcaction bucket of account B + mhashes, err := b.BuildMerkleTree().MerkleProof(bucket.Hash()) + if err != nil { + t.Error(err) + } + // Create a new Merkle proof for the transaction bucket + merkleProof := protocol.NewMerkleProof(b.Height, mhashes, bucket.Address, bucket.RelativeBalance, bucket.CalculateMerkleRoot()) + + // Create a new transaction that contains the previous SCP + tx1, _ := protocol.NewSignedFundsTx(0x01, 39, 1, uint32(0), accB.Address, accA.Address, PrivKeyAccB, nil) + // Current Balance of Account B: 60 + tx1.Proofs = append(tx1.Proofs, &merkleProof) + + + // Create a new transaction that contains the previous SCP + tx2, _ := protocol.NewSignedFundsTx(0x01, 39, 1, uint32(1), accB.Address, accA.Address, PrivKeyAccB, nil) + // Current Balance of Account B: 20 + tx2.Proofs = append(tx2.Proofs, &merkleProof) + + // Verify if the SCP is valid + if err := verifyFundsTransactions([]*protocol.FundsTx{tx1, tx2}, []*protocol.Block{b}); err != nil { + t.Error(err) + } + + // Repeat previous step: Create another new block + b1 := protocol.NewBlock(b.Hash, 1) + if err := addTx(b1, tx1); err != nil { + t.Error(err) + } + + if err := addTx(b1, tx2); err != nil { + t.Error(err) + } + + bucket, exists = b1.TxBuckets[accB.Address] + if !exists { + t.Errorf("transaction bucket is not part of the block for address %x", accB.Address[0:8]) + } + + storage.WriteOpenTx(tx1) + storage.WriteOpenTx(tx2) + finalizeBlock(b1) + + mhashes, err = b1.BuildMerkleTree().MerkleProof(bucket.Hash()) + if err != nil { + t.Error(err) + } + merkleProof1 := protocol.NewMerkleProof(b1.Height, mhashes, bucket.Address, bucket.RelativeBalance, bucket.CalculateMerkleRoot()) + + // Create another transaction that contains SCP + tx3, _ := protocol.NewSignedFundsTx(0x01, 19, 1, uint32(2), accB.Address, accA.Address, PrivKeyAccB, nil) + + // Current Balance of Account B: 0 + + tx3.Proofs = append(tx3.Proofs, &merkleProof1, &merkleProof) + if err := verifyFundsTransactions([]*protocol.FundsTx{tx3}, []*protocol.Block{b1, b}); err != nil { + t.Errorf("self-contained proof should be valid (sufficient funds) but verifySCP returns error %v", err) + } +} + +// Test veirifySCP with a block that contains two transactions in one block (insufficient funds) +func TestVerifySCP2TI(t *testing.T) { + cleanAndPrepare() + + // Setup test by transferring 100 coins to account B + b := protocol.NewBlock([32]byte{}, 0) + tx, _ := protocol.NewSignedFundsTx(0x01, 100, 1, uint32(0), accA.Address, accB.Address, PrivKeyAccA, nil) + + // Current Balance of Account B: 100 + + if err := addTx(b, tx); err != nil { + t.Error(err) + } + + // Get the transaction bucket of account B and check if it exists + bucket, exists := b.TxBuckets[accB.Address] + if !exists { + t.Errorf("transaction bucket is not part of the block for address %x", accA.Address[0:8]) + } + + // Finalize the first block + storage.WriteOpenTx(tx) + finalizeBlock(b) + + // Get the Merkle proof for the transcaction bucket of account B + mhashes, err := b.BuildMerkleTree().MerkleProof(bucket.Hash()) + if err != nil { + t.Error(err) + } + // Create a new Merkle proof for the transaction bucket + merkleProof := protocol.NewMerkleProof(b.Height, mhashes, bucket.Address, bucket.RelativeBalance, bucket.CalculateMerkleRoot()) + + // Create a new transaction that contains the previous SCP + tx1, _ := protocol.NewSignedFundsTx(0x01, 39, 1, uint32(0), accB.Address, accA.Address, PrivKeyAccB, nil) + // Current Balance of Account B: 60 + tx1.Proofs = append(tx1.Proofs, &merkleProof) + + + // Create a new transaction that contains the previous SCP + tx2, _ := protocol.NewSignedFundsTx(0x01, 69, 1, uint32(1), accB.Address, accA.Address, PrivKeyAccB, nil) + // Current Balance of Account B: -10 -> SHOULD RETURN ERROR + tx2.Proofs = append(tx2.Proofs, &merkleProof) + + // Verify if the SCP is valid (should be not) + if err := verifyFundsTransactions([]*protocol.FundsTx{tx1, tx2}, []*protocol.Block{b}); err == nil { + t.Error("self-contained proof should be invalid (insufficient funds) but verifySCP returns no error") + } +} \ No newline at end of file diff --git a/miner/staterollback_test.go b/miner/staterollback_test.go index 2aee0bf..c92b4bd 100644 --- a/miner/staterollback_test.go +++ b/miner/staterollback_test.go @@ -1,7 +1,6 @@ package miner import ( - "github.com/bazo-blockchain/bazo-miner/crypto" "math/rand" "reflect" "testing" @@ -20,7 +19,7 @@ func TestFundsStateChangeRollback(t *testing.T) { var testSize uint32 testSize = 1000 - b := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + b := protocol.NewBlock([32]byte{}, 1) var funds []*protocol.FundsTx var feeA, feeB uint64 @@ -35,7 +34,7 @@ func TestFundsStateChangeRollback(t *testing.T) { loopMax := int(randVar.Uint32()%testSize + 1) for i := 0; i < loopMax+1; i++ { - ftx, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%1000000+1, randVar.Uint64()%100+1, uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) + ftx, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%1000000+1, randVar.Uint64()%100+1, uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) if addTx(b, ftx) == nil { funds = append(funds, ftx) balanceA -= ftx.Amount @@ -46,7 +45,7 @@ func TestFundsStateChangeRollback(t *testing.T) { t.Errorf("Block rejected a valid transaction: %v\n", ftx) } - ftx2, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%1000+1, randVar.Uint64()%100+1, uint32(i), accB.Address, accA.Address, PrivKeyAccB, nil) + ftx2, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%1000+1, randVar.Uint64()%100+1, uint32(i), accB.Address, accA.Address, PrivKeyAccB, nil) if addTx(b, ftx2) == nil { funds = append(funds, ftx2) balanceB -= ftx2.Amount @@ -155,7 +154,7 @@ func TestCollectTxFeesRollback(t *testing.T) { var fee uint64 loopMax := int(randVar.Uint64() % 1000) for i := 0; i < loopMax+1; i++ { - tx, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%1000000+1, randVar.Uint64()%100+1, uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) + tx, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%1000000+1, randVar.Uint64()%100+1, uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) funds = append(funds, tx) fee += tx.Fee @@ -175,7 +174,7 @@ func TestCollectTxFeesRollback(t *testing.T) { minerBal = validatorAcc.Balance //Miner gets fees, the miner account balance will overflow at some point for i := 2; i < 100; i++ { - tx, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%1000000+1, uint64(i), uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) + tx, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%1000000+1, uint64(i), uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) funds2 = append(funds2, tx) fee2 += tx.Fee } @@ -183,7 +182,7 @@ func TestCollectTxFeesRollback(t *testing.T) { accABal := accA.Balance accBBal := accB.Balance //Should throw an error and result in a rollback, because of acc balance overflow - tmpBlock := newBlock([32]byte{}, [crypto.COMM_PROOF_LENGTH]byte{}, 1) + tmpBlock := protocol.NewBlock([32]byte{}, 1) tmpBlock.Beneficiary = validatorAcc.Address data := blockData{nil, funds2, nil, nil, tmpBlock} if err := validateState(data); err == nil || diff --git a/miner/verification.go b/miner/verification.go index 82b28d2..c9a755a 100644 --- a/miner/verification.go +++ b/miner/verification.go @@ -2,6 +2,8 @@ package miner import ( "crypto/ecdsa" + "errors" + "fmt" "github.com/bazo-blockchain/bazo-miner/crypto" "github.com/bazo-blockchain/bazo-miner/protocol" "github.com/bazo-blockchain/bazo-miner/storage" @@ -12,34 +14,37 @@ import ( //the verify method. This is because verification depends on the State (e.g., dynamic properties), which //should only be of concern to the miner, not to the protocol package. However, this has the disadvantage //that we have to do case distinction here. -func verify(tx protocol.Transaction) bool { - var verified bool +func verify(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 { + return errors.New(fmt.Sprintf("Transaction fee too low: %v (minimum is: %v)\n", tx.TxFee(), activeParameters.Fee_minimum)) + } switch tx.(type) { case *protocol.FundsTx: - verified = verifyFundsTx(tx.(*protocol.FundsTx)) + return verifyFundsTx(tx.(*protocol.FundsTx)) case *protocol.ContractTx: - verified = verifyContractTx(tx.(*protocol.ContractTx)) + return verifyContractTx(tx.(*protocol.ContractTx)) case *protocol.ConfigTx: - verified = verifyConfigTx(tx.(*protocol.ConfigTx)) + return verifyConfigTx(tx.(*protocol.ConfigTx)) case *protocol.StakeTx: - verified = verifyStakeTx(tx.(*protocol.StakeTx)) + return verifyStakeTx(tx.(*protocol.StakeTx)) } - return verified + return errors.New("could not match a transaction type for verification") } -func verifyFundsTx(tx *protocol.FundsTx) bool { +func verifyFundsTx(tx *protocol.FundsTx) error { if tx == nil { - return false + return errors.New("transaction does not exist") } r, s := new(big.Int), new(big.Int) //fundsTx only makes sense if amount > 0 if tx.Amount == 0 || tx.Amount > MAX_MONEY { - logger.Printf("Invalid transaction amount: %v\n", tx.Amount) - return false + return errors.New(fmt.Sprintf("Invalid transaction amount: %v\n", tx.Amount)) } accFromHash := protocol.SerializeHashContent(tx.From) @@ -52,16 +57,15 @@ func verifyFundsTx(tx *protocol.FundsTx) bool { pubKey := crypto.GetPubKeyFromAddress(tx.From) if ecdsa.Verify(pubKey, txHash[:], r, s) && tx.From != tx.To { - return true + return nil } else { - logger.Printf("Sig invalid. FromHash: %x\nToHash: %x\n", accFromHash[0:8], accToHash[0:8]) - return false + return errors.New(fmt.Sprintf("Sig invalid. FromHash: %x\nToHash: %x\n", accFromHash[0:8], accToHash[0:8])) } } -func verifyContractTx(tx *protocol.ContractTx) bool { +func verifyContractTx(tx *protocol.ContractTx) error { if tx == nil { - return false + return errors.New("transaction does not exist") } r, s := new(big.Int), new(big.Int) @@ -75,16 +79,16 @@ func verifyContractTx(tx *protocol.ContractTx) bool { //Only the hash of the pubkey is hashed and verified here if ecdsa.Verify(pubKey, txHash[:], r, s) == true { - return true + return nil } } - return false + return errors.New("contracttx could not be verified") } -func verifyConfigTx(tx *protocol.ConfigTx) bool { +func verifyConfigTx(tx *protocol.ConfigTx) error { if tx == nil { - return false + return errors.New("transaction does not exist") } //account creation can only be done with a valid priv/pub key which is hard-coded @@ -97,17 +101,16 @@ func verifyConfigTx(tx *protocol.ConfigTx) bool { pubKey := crypto.GetPubKeyFromAddress(rootAcc.Address) txHash := tx.Hash() if ecdsa.Verify(pubKey, txHash[:], r, s) == true { - return true + return nil } } - return false + return errors.New("configtx could not be verified") } -func verifyStakeTx(tx *protocol.StakeTx) bool { +func verifyStakeTx(tx *protocol.StakeTx) error { if tx == nil { - logger.Println("Transactions does not exist.") - return false + return errors.New("transaction does not exist") } //Check if account is present in the actual state @@ -130,7 +133,11 @@ func verifyStakeTx(tx *protocol.StakeTx) bool { pubKey := crypto.GetPubKeyFromAddress(acc.Address) - return ecdsa.Verify(pubKey, txHash[:], r, s) + if ecdsa.Verify(pubKey, txHash[:], r, s) { + return nil + } + + return errors.New("staketx could not be verified") } //Returns true if id is in the list of possible ids and rational value for payload parameter. diff --git a/miner/verification_test.go b/miner/verification_test.go index b12cf71..4474bc2 100644 --- a/miner/verification_test.go +++ b/miner/verification_test.go @@ -13,8 +13,8 @@ func TestFundsTxVerification(t *testing.T) { loopMax := int(randVar.Uint64() % 1000) for i := 0; i < loopMax; i++ { - tx, _ := protocol.ConstrFundsTx(0x01, randVar.Uint64()%100000+1, randVar.Uint64()%10+1, uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) - if verifyFundsTx(tx) == false { + tx, _ := protocol.NewSignedFundsTx(0x01, randVar.Uint64()%100000+1, randVar.Uint64()%10+1, uint32(i), accA.Address, accB.Address, PrivKeyAccA, nil) + if verifyFundsTx(tx) != nil { t.Errorf("Tx could not be verified: \n%v", tx) } } @@ -27,7 +27,7 @@ func TestContractTx(t *testing.T) { loopMax := int(randVar.Uint64() % 1000) for i := 0; i <= loopMax; i++ { tx, _, _ := protocol.ConstrContractTx(0, randVar.Uint64()%100+1, PrivKeyRoot, nil, nil) - if verifyContractTx(tx) == false { + if verifyContractTx(tx) != nil { t.Errorf("ContractTx could not be verified: %v\n", tx) } } @@ -46,12 +46,12 @@ func TestConfigTx(t *testing.T) { //Add an invalid configTx, should not be accepted txfail, err6 := protocol.ConstrConfigTx(uint8(randVar.Uint32()%256), 20, 5000, randVar.Uint64(), 0, PrivKeyRoot) - if (verifyConfigTx(tx) == false || err != nil) && - (verifyConfigTx(tx2) == false || err2 != nil) && - (verifyConfigTx(tx3) == false || err3 != nil) && - (verifyConfigTx(tx4) == false || err4 != nil) && - (verifyConfigTx(tx5) == false || err5 != nil) && - (verifyConfigTx(txfail) == true || err6 != nil) { + if (verifyConfigTx(tx) != nil || err != nil) && + (verifyConfigTx(tx2) != nil || err2 != nil) && + (verifyConfigTx(tx3) != nil || err3 != nil) && + (verifyConfigTx(tx4) != nil || err4 != nil) && + (verifyConfigTx(tx5) != nil || err5 != nil) && + (verifyConfigTx(txfail) != nil || err6 != nil) { t.Error("ConfigTx verification malfunctioning!") } } diff --git a/p2p/response.go b/p2p/response.go index 79f7e81..f10a807 100644 --- a/p2p/response.go +++ b/p2p/response.go @@ -93,12 +93,10 @@ func blockHeaderRes(p *peer, payload []byte) { var blockHash [32]byte copy(blockHash[:], payload[:32]) if block := storage.ReadClosedBlock(blockHash); block != nil { - block.InitBloomFilter(append(storage.GetTxPubKeys(block))) encodedHeader = block.EncodeHeader() } } else { if block := storage.ReadLastClosedBlock(); block != nil { - block.InitBloomFilter(append(storage.GetTxPubKeys(block))) encodedHeader = block.EncodeHeader() } } @@ -233,9 +231,9 @@ func intermediateNodesRes(p *peer, payload []byte) { copy(blockHash[:], payload[:32]) copy(txHash[:], payload[32:64]) - merkleTree := protocol.BuildMerkleTree(storage.ReadClosedBlock(blockHash)) + merkleTree := storage.ReadClosedBlock(blockHash).BuildMerkleTree() - if intermediates, _ := protocol.GetIntermediate(protocol.GetLeaf(merkleTree, txHash)); intermediates != nil { + if intermediates, _ := protocol.GetIntermediate(merkleTree.GetLeaf(txHash)); intermediates != nil { for _, node := range intermediates { nodeHashes = append(nodeHashes, node.Hash[:]) } diff --git a/protocol/account.go b/protocol/account.go index e75bbf1..e71c832 100644 --- a/protocol/account.go +++ b/protocol/account.go @@ -72,12 +72,16 @@ func (acc *Account) Encode() []byte { return buffer.Bytes() } -func (*Account) Decode(encoded []byte) (acc *Account) { +func (*Account) Decode(encoded []byte) (acc *Account, err error) { var decoded Account buffer := bytes.NewBuffer(encoded) decoder := gob.NewDecoder(buffer) - decoder.Decode(&decoded) - return &decoded + err = decoder.Decode(&decoded) + if err != nil { + return nil, err + } + + return &decoded, nil } func (acc Account) String() string { diff --git a/protocol/account_test.go b/protocol/account_test.go index 99de483..4495c9d 100644 --- a/protocol/account_test.go +++ b/protocol/account_test.go @@ -66,7 +66,7 @@ func TestAccountSerialization(t *testing.T) { var compareAcc *Account encodedAcc := accA.Encode() - compareAcc = compareAcc.Decode(encodedAcc) + compareAcc, _ = compareAcc.Decode(encodedAcc) if !reflect.DeepEqual(accA, compareAcc) { t.Error("Account encoding/decoding failed!") diff --git a/protocol/block.go b/protocol/block.go index 302926e..73ea1d3 100644 --- a/protocol/block.go +++ b/protocol/block.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/gob" "fmt" - "github.com/bazo-blockchain/bazo-miner/crypto" "github.com/willf/bloom" ) @@ -12,7 +11,7 @@ import ( const ( TXHASH_LEN = 32 HEIGHT_LEN = 4 - MIN_BLOCKHEADER_SIZE = 136 + MIN_BLOCKHEADER_SIZE = 144 MIN_BLOCKSIZE = 184 + MIN_BLOCKHEADER_SIZE + crypto.COMM_PROOF_LENGTH BLOOM_FILTER_ERROR_RATE = 0.1 ) @@ -27,6 +26,7 @@ type Block struct { BloomFilter *bloom.BloomFilter Height uint32 Beneficiary [64]byte + TotalFees uint64 //Body Nonce [8]byte @@ -35,16 +35,19 @@ type Block struct { NrContractTx uint16 NrFundsTx uint16 NrStakeTx uint16 + NrTxBucket uint16 SlashedAddress [64]byte CommitmentProof [crypto.COMM_PROOF_LENGTH]byte ConflictingBlockHash1 [32]byte ConflictingBlockHash2 [32]byte - StateCopy map[[64]byte]*Account //won't be serialized, just keeping track of local state changes + StateCopy map[[64]byte]*Account // won't be serialized, just keeping track of local state changes + TxBuckets txBucketMap // won't be serialized, just keeping track of transaction buckets ContractTxData [][32]byte FundsTxData [][32]byte ConfigTxData [][32]byte StakeTxData [][32]byte + TxBucketData [][32]byte } func NewBlock(prevHash [32]byte, height uint32) *Block { @@ -54,10 +57,75 @@ func NewBlock(prevHash [32]byte, height uint32) *Block { } newBlock.StateCopy = make(map[[64]byte]*Account) + newBlock.TxBuckets = NewTxBucketMap() return &newBlock } +func (block *Block) AddFundsTx(tx *FundsTx) { + bucket := block.createBucketIfNotExists(tx.From) + bucket.AddFundsTx(tx) + + bucket = block.createBucketIfNotExists(tx.To) + bucket.AddFundsTx(tx) + + block.FundsTxData = append(block.FundsTxData, tx.Hash()) +} + +func (block *Block) createBucketIfNotExists(address AddressType) *TxBucket { + bucket, exists := block.TxBuckets[address] + if !exists { + bucket = NewTxBucket(address) + block.TxBuckets[address] = bucket + } + return bucket +} + +func (block *Block) BuildMerkleTree() *MerkleTree { + var hashes HashArray + + if block == nil { + return nil + } + + // Note that the Merkle roots of the buckets are + // the leaves of the Merkle tree, not the FundsTx itselves + /*for _, bucket := range block.TxBuckets { + hashes = append(hashes, bucket.Hash()) + }*/ + + if block.TxBucketData != nil { + for _, hash := range block.TxBucketData { + hashes = append(hashes, hash) + } + } + if block.ContractTxData != nil { + for _, txHash := range block.ContractTxData { + hashes = append(hashes, txHash) + } + } + if block.ConfigTxData != nil { + for _, txHash := range block.ConfigTxData { + hashes = append(hashes, txHash) + } + } + + if block.StakeTxData != nil { + for _, txHash := range block.StakeTxData { + hashes = append(hashes, txHash) + } + } + + //Merkle root for no transactions is 0 hash + if len(hashes) == 0 { + return nil + } + + m, _ := NewMerkleTree(hashes) + + return m +} + func (block *Block) HashBlock() [32]byte { if block == nil { return [32]byte{} @@ -68,6 +136,7 @@ func (block *Block) HashBlock() [32]byte { timestamp int64 merkleRoot [32]byte beneficiary [64]byte + totalFees uint64 commitmentProof [crypto.COMM_PROOF_LENGTH]byte slashedAddress [64]byte conflictingBlockHash1 [32]byte @@ -77,6 +146,7 @@ func (block *Block) HashBlock() [32]byte { block.Timestamp, block.MerkleRoot, block.Beneficiary, + block.TotalFees, block.CommitmentProof, block.SlashedAddress, block.ConflictingBlockHash1, @@ -103,7 +173,8 @@ func (block *Block) GetSize() uint64 { int(block.NrContractTx)*TXHASH_LEN + int(block.NrFundsTx)*TXHASH_LEN + int(block.NrConfigTx)*TXHASH_LEN + - int(block.NrStakeTx)*TXHASH_LEN + int(block.NrStakeTx)*TXHASH_LEN + + int(block.NrTxBucket)*TXHASH_LEN if block.BloomFilter != nil { encodedBF, _ := block.BloomFilter.GobEncode() @@ -125,11 +196,13 @@ func (block *Block) Encode() []byte { Nonce: block.Nonce, Timestamp: block.Timestamp, MerkleRoot: block.MerkleRoot, + TotalFees: block.TotalFees, Beneficiary: block.Beneficiary, - NrContractTx: block.NrContractTx, + NrContractTx: block.NrContractTx, NrFundsTx: block.NrFundsTx, NrConfigTx: block.NrConfigTx, NrStakeTx: block.NrStakeTx, + NrTxBucket: block.NrTxBucket, NrElementsBF: block.NrElementsBF, BloomFilter: block.BloomFilter, SlashedAddress: block.SlashedAddress, @@ -142,6 +215,7 @@ func (block *Block) Encode() []byte { FundsTxData: block.FundsTxData, ConfigTxData: block.ConfigTxData, StakeTxData: block.StakeTxData, + TxBucketData: block.TxBucketData, } buffer := new(bytes.Buffer) @@ -163,6 +237,7 @@ func (block *Block) EncodeHeader() []byte { BloomFilter: block.BloomFilter, Height: block.Height, Beneficiary: block.Beneficiary, + TotalFees: block.TotalFees, } buffer := new(bytes.Buffer) @@ -179,6 +254,10 @@ func (block *Block) Decode(encoded []byte) (b *Block) { buffer := bytes.NewBuffer(encoded) decoder := gob.NewDecoder(buffer) decoder.Decode(&decoded) + + decoded.StateCopy = make(map[[64]byte]*Account) + decoded.TxBuckets = NewTxBucketMap() + return &decoded } @@ -189,10 +268,12 @@ func (block Block) String() string { "Timestamp: %v\n"+ "MerkleRoot: %x\n"+ "Beneficiary: %x\n"+ + "Total Fees: %v\n"+ "Amount of fundsTx: %v\n"+ "Amount of contractTx: %v\n"+ "Amount of configTx: %v\n"+ "Amount of stakeTx: %v\n"+ + "Amount of txBuckets: %v\n"+ "Height: %d\n"+ "Commitment Proof: %x\n"+ "Slashed Address:%x\n"+ @@ -204,10 +285,12 @@ func (block Block) String() string { block.Timestamp, block.MerkleRoot[0:8], block.Beneficiary[0:8], + block.TotalFees, block.NrFundsTx, block.NrContractTx, block.NrConfigTx, block.NrStakeTx, + block.NrTxBucket, block.Height, block.CommitmentProof[0:8], block.SlashedAddress[0:8], diff --git a/protocol/block_test.go b/protocol/block_test.go index b8a7d0c..53d4277 100644 --- a/protocol/block_test.go +++ b/protocol/block_test.go @@ -57,8 +57,7 @@ func TestBlockHash(t *testing.T) { func TestBlockSerialization(t *testing.T) { randVar := rand.New(rand.NewSource(time.Now().Unix())) - var block Block - + block := NewBlock([32]byte{}, 0) block.Header = 1 rand.Read(block.Hash[:]) rand.Read(block.PrevHash[:]) @@ -70,26 +69,26 @@ func TestBlockSerialization(t *testing.T) { block.NrFundsTx = uint16(randVar.Uint32()) block.NrConfigTx = uint8(randVar.Uint32()) block.NrStakeTx = uint16(randVar.Uint32()) + block.NrTxBucket = uint16(randVar.Uint32()) rand.Read(block.SlashedAddress[:]) block.Height = uint32(randVar.Uint32()) rand.Read(block.CommitmentProof[:]) rand.Read(block.ConflictingBlockHash1[:]) rand.Read(block.ConflictingBlockHash2[:]) - var compareBlock Block + var compareBlock *Block encodedBlock := block.Encode() - compareBlock = *compareBlock.Decode(encodedBlock) + compareBlock = compareBlock.Decode(encodedBlock) if !reflect.DeepEqual(block, compareBlock) { - t.Error("Block encoding/decoding failed!") + t.Errorf("Block encoding/decoding failed, %v vs %v", block.String(), compareBlock.String()) } } func TestBlockHeaderSerialization(t *testing.T) { randVar := rand.New(rand.NewSource(time.Now().Unix())) - var blockHeader Block - + blockHeader := NewBlock([32]byte{}, 0) blockHeader.Header = 1 rand.Read(blockHeader.Hash[:]) rand.Read(blockHeader.PrevHash[:]) @@ -106,9 +105,9 @@ func TestBlockHeaderSerialization(t *testing.T) { blockHeader.Height = uint32(randVar.Uint32()) rand.Read(blockHeader.Beneficiary[:]) - var compareBlockHeader Block + var compareBlockHeader *Block encodedBlock := blockHeader.EncodeHeader() - compareBlockHeader = *compareBlockHeader.Decode(encodedBlock) + compareBlockHeader = compareBlockHeader.Decode(encodedBlock) if !reflect.DeepEqual(blockHeader, compareBlockHeader) { t.Error("Block encoding/decoding failed!") diff --git a/protocol/byteArray.go b/protocol/byteArray.go deleted file mode 100644 index b1ed09a..0000000 --- a/protocol/byteArray.go +++ /dev/null @@ -1,3 +0,0 @@ -package protocol - -type ByteArray []byte diff --git a/protocol/fundstx.go b/protocol/fundstx.go index d9c557f..2375101 100644 --- a/protocol/fundstx.go +++ b/protocol/fundstx.go @@ -23,9 +23,23 @@ type FundsTx struct { To [64]byte Sig [64]byte Data []byte + Proofs []*MerkleProof } -func ConstrFundsTx(header byte, amount uint64, fee uint64, txCnt uint32, from, to [64]byte, sigKey *ecdsa.PrivateKey, data []byte) (tx *FundsTx, err error) { +func NewSimpleFundsTx(amount uint64, fee uint64, txCnt uint32, from, to [64]byte) (tx *FundsTx) { + tx = new(FundsTx) + + tx.Header = 0x01 + tx.From = from + tx.To = to + tx.Amount = amount + tx.Fee = fee + tx.TxCnt = txCnt + + return tx +} + +func NewSignedFundsTx(header byte, amount uint64, fee uint64, txCnt uint32, from, to [64]byte, sigKey *ecdsa.PrivateKey, data []byte) (tx *FundsTx, err error) { tx = new(FundsTx) tx.Header = header @@ -79,6 +93,8 @@ func (tx *FundsTx) Hash() (hash [32]byte) { //when we serialize the struct with binary.Write, unexported field get serialized as well, undesired //behavior. Therefore, writing own encoder/decoder func (tx *FundsTx) Encode() (encodedTx []byte) { + gob.Register(MerkleProof{}) + // Encode encodeData := FundsTx{ tx.Header, @@ -89,6 +105,7 @@ func (tx *FundsTx) Encode() (encodedTx []byte) { tx.To, tx.Sig, tx.Data, + tx.Proofs, } buffer := new(bytes.Buffer) gob.NewEncoder(buffer).Encode(encodeData) @@ -115,7 +132,8 @@ func (tx FundsTx) String() string { "From: %x\n"+ "To: %x\n"+ "Sig: %x\n"+ - "Data: %v\n", + "Data: %v\n" + + "SCP Length: %v\n", tx.Header, tx.Amount, tx.Fee, @@ -124,5 +142,6 @@ func (tx FundsTx) String() string { tx.To[0:8], tx.Sig[0:8], tx.Data, + len(tx.Proofs), ) } diff --git a/protocol/fundstx_test.go b/protocol/fundstx_test.go index a3879c6..6808a1d 100644 --- a/protocol/fundstx_test.go +++ b/protocol/fundstx_test.go @@ -11,7 +11,21 @@ func TestFundsTxSerialization(t *testing.T) { rand := rand.New(rand.NewSource(time.Now().Unix())) loopMax := int(rand.Uint32() % 10000) for i := 0; i < loopMax; i++ { - tx, _ := ConstrFundsTx(0x01, rand.Uint64()%100000+1, rand.Uint64()%10+1, uint32(i), accA.Address, accB.Address, PrivKeyA, nil) + tx, _ := NewSignedFundsTx(0x01, rand.Uint64()%100000+1, rand.Uint64()%10+1, uint32(i), accA.Address, accB.Address, PrivKeyA, nil) + + var merkleRootsBefore [][32]byte + proofs := getDummyProofs() + + for i := 0; i < len(proofs); i++ { + merkleRoot, err := proofs[i].CalculateMerkleRoot() + if err != nil { + t.Error("failed to calculate merkle root") + } + merkleRootsBefore = append(merkleRootsBefore, merkleRoot) + } + + tx.Proofs = proofs + data := tx.Encode() var decodedTx *FundsTx decodedTx = decodedTx.Decode(data) @@ -23,5 +37,57 @@ func TestFundsTxSerialization(t *testing.T) { if !reflect.DeepEqual(tx, decodedTx) { t.Errorf("FundsTx Serialization failed (%v) vs. (%v)\n", tx, decodedTx) } + + for i := 0; i < len(proofs); i++ { + if !reflect.DeepEqual(decodedTx.Proofs[i], tx.Proofs[i]) { + t.Errorf("Proof does not match the given one: %v vs. %v", decodedTx.Proofs[i].String(), tx.Proofs[i].String()) + } + + merkleRootBefore := merkleRootsBefore[i] + merkleRootAfter, err := decodedTx.Proofs[i].CalculateMerkleRoot() + if err != nil { + t.Error("failed to calculate merkle root") + } + + if merkleRootBefore != merkleRootAfter { + t.Errorf("Merkle proof serialization failed: Merkle roots not the same \n(%v)\nvs.\n(%v)\n", merkleRootAfter, merkleRootsBefore[i]) + } + } } } + +func getDummyProofs() (proofs []*MerkleProof) { + randVal := rand.New(rand.NewSource(time.Now().Unix())) + nofProofs := int(randVal.Uint32() % 10) + 1 + + for i := 0; i < nofProofs; i++ { + randHeight := (randVal.Uint32() % 10) + uint32(i * 10) + var address AddressType + var merkleRoot HashType + randVal.Read(address[:]) + randVal.Read(merkleRoot[:]) + proof := NewMerkleProof(randHeight, [][33]byte{}, address, randVal.Int63()%100000+1, merkleRoot) + + merkleTreeDepth := int(rand.Uint32() % 10) + 1 + for j:= 0; j < merkleTreeDepth; j++ { + leftOrRightNumber := int(rand.Uint32() % 2) + + var mhash [33]byte + var leftOrRight [1]byte + + if leftOrRightNumber == 0 { + leftOrRight = [1]byte{'l'} + } else { + leftOrRight = [1]byte{'r'} + } + + copy(mhash[0:1], leftOrRight[:]) + randVal.Read(mhash[1:33]) + proof.MerkleHashes = append(proof.MerkleHashes, mhash) + } + + proofs = append(proofs, &proof) + } + + return proofs +} \ No newline at end of file diff --git a/protocol/merkleproof.go b/protocol/merkleproof.go new file mode 100644 index 0000000..f8efc99 --- /dev/null +++ b/protocol/merkleproof.go @@ -0,0 +1,133 @@ +package protocol + +import ( + "bytes" + "encoding/gob" + "fmt" +) + +type MerkleProof struct { + // Proof height + Height uint32 + + // Merkle hashes + // Intermediate hashes required to create a Merkle proof + // Note that the first byte specifies the left or right node of the Merkle tree while the rest is the actual hash + MerkleHashes [][33]byte + + // Bucket properties + // Must equal the hashed data of TxBucket.Hash() + BucketAddress AddressType + BucketRelativeBalance int64 + BucketMerkleRoot HashType +} + + +func NewMerkleProof(height uint32, mhashes [][33]byte, address AddressType, amount int64, merkleRoot HashType) (proof MerkleProof) { + proof.Height = height + proof.MerkleHashes = mhashes + proof.BucketAddress = address + proof.BucketRelativeBalance = amount + proof.BucketMerkleRoot = merkleRoot + + return proof +} + +func (proof *MerkleProof) Hash() (hash [32]byte) { + if proof == nil { + return [32]byte{} + } + + input := struct { + Height uint32 + MHashes [][33]byte + Address AddressType + Amount int64 + MerkleRoot HashType + }{ + proof.Height, + proof.MerkleHashes, + proof.BucketAddress, + proof.BucketRelativeBalance, + proof.BucketMerkleRoot, + } + + return SerializeHashContent(input) +} + +func (proof *MerkleProof) Encode() (encodedTx []byte) { + encodeData := MerkleProof{ + proof.Height, + proof.MerkleHashes, + proof.BucketAddress, + proof.BucketRelativeBalance, + proof.BucketMerkleRoot, + } + buffer := new(bytes.Buffer) + gob.NewEncoder(buffer).Encode(encodeData) + return buffer.Bytes() +} + +func (proof *MerkleProof) Decode(encoded []byte) *MerkleProof { + var decoded MerkleProof + buffer := bytes.NewBuffer(encoded) + decoder := gob.NewDecoder(buffer) + decoder.Decode(&decoded) + return &decoded +} + +func (proof *MerkleProof) String() string { + var mhashesString string + for _, mhash := range proof.MerkleHashes { + mhashesString += fmt.Sprintf("%x, ", mhash[0:8]) + } + + mhashesString = mhashesString[0:len(mhashesString) - 2] + + return fmt.Sprintf("Height: %v\n" + + "MerkleHashes: [%v]\n" + + "Bucket BucketAddress: %v\n" + + "Bucket BucketRelativeBalance: %v\n"+ + "Bucket BucketMerkleRoot: %v\n", + proof.Height, + mhashesString, + proof.BucketAddress[0:8], + proof.BucketRelativeBalance, + proof.BucketMerkleRoot[0:8], + ) +} + +func (proof *MerkleProof) CalculateMerkleRoot() (computedHash [32]byte, err error) { + bucketHash := proof.getBucketHash() + + computedHash = bucketHash + for _, mhash := range proof.MerkleHashes { + var hash [32]byte + var leftOrRight [1]byte + copy(leftOrRight[:], mhash[0:1]) + copy(hash[:], mhash[1:33]) + + if leftOrRight == [1]byte{'l'} { + computedHash = MTHash(append(hash[:], computedHash[:]...)) + } else { + computedHash = MTHash(append(computedHash[:], hash[:]...)) + } + } + + return computedHash, nil +} + +func (proof *MerkleProof) getBucketHash() [32]byte { + // Note that the hashed properties must equal to the hashed properties of TxBucket.Hash() + input := struct { + Address AddressType + Amount int64 + MerkleRoot HashType + }{ + proof.BucketAddress, + proof.BucketRelativeBalance, + proof.BucketMerkleRoot, + } + + return SerializeHashContent(input) +} \ No newline at end of file diff --git a/protocol/merkleproof_test.go b/protocol/merkleproof_test.go new file mode 100644 index 0000000..cf13458 --- /dev/null +++ b/protocol/merkleproof_test.go @@ -0,0 +1,58 @@ +package protocol + +import ( + "math/rand" + "reflect" + "testing" + "time" +) + +func TestMerkleProofSerialization(t *testing.T) { + randVal := rand.New(rand.NewSource(time.Now().Unix())) + randHeight := randVal.Uint32() % 10 + var address AddressType + var merkleRoot HashType + randVal.Read(address[:]) + randVal.Read(merkleRoot[:]) + + proof := NewMerkleProof(randHeight, [][33]byte{}, address, randVal.Int63()%100000+1, merkleRoot) + merkleTreeDepth := int(rand.Uint32() % 10) + 1 + for j:= 0; j < merkleTreeDepth; j++ { + leftOrRightNumber := int(rand.Uint32() % 2) + + var mhash [33]byte + var leftOrRight [1]byte + if leftOrRightNumber == 0 { + leftOrRight = [1]byte{'l'} + } else { + leftOrRight = [1]byte{'r'} + } + + copy(mhash[0:1], leftOrRight[:]) + rand.Read(mhash[1:33]) + proof.MerkleHashes = append(proof.MerkleHashes, mhash) + } + + merkleRootBefore, err := proof.CalculateMerkleRoot() + if err != nil { + t.Error(err) + } + + encoded := proof.Encode() + + var decoded *MerkleProof + decoded = decoded.Decode(encoded) + + if !reflect.DeepEqual(&proof, decoded) { + t.Errorf("Proof does not match the given one: \n%vvs.\n%v", proof.String(), decoded.String()) + } + + merkleRootAfter, err := decoded.CalculateMerkleRoot() + if err != nil { + t.Error("failed to calculate merkle root") + } + + if merkleRootBefore != merkleRootAfter { + t.Errorf("SCP serialization failed: Merkle roots not the same \n(%v)\nvs.\n(%v)\n", merkleRootBefore, merkleRootAfter) + } +} \ No newline at end of file diff --git a/protocol/merkletree.go b/protocol/merkletree.go index f065754..1eeb9db 100644 --- a/protocol/merkletree.go +++ b/protocol/merkletree.go @@ -27,59 +27,8 @@ type Node struct { Hash [32]byte } -//verifyNode walks down the tree until hitting a leaf, calculating the hash at each level -//and returning the resulting hash of Node n. -func (n *Node) verifyNode() [32]byte { - if n.leaf { - return n.Hash - } - leftHash := n.Left.verifyNode() - rightHash := n.Right.verifyNode() - concatHash := append(leftHash[:], rightHash[:]...) - return sha3.Sum256(concatHash) -} - -func BuildMerkleTree(b *Block) *MerkleTree { - var txHashes [][32]byte - - if b == nil { - return nil - } - - if b.FundsTxData != nil { - for _, txHash := range b.FundsTxData { - txHashes = append(txHashes, txHash) - } - } - if b.ContractTxData != nil { - for _, txHash := range b.ContractTxData { - txHashes = append(txHashes, txHash) - } - } - if b.ConfigTxData != nil { - for _, txHash := range b.ConfigTxData { - txHashes = append(txHashes, txHash) - } - } - - if b.StakeTxData != nil { - for _, txHash := range b.StakeTxData { - txHashes = append(txHashes, txHash) - } - } - - //Merkle root for no transactions is 0 hash - if len(txHashes) == 0 { - return nil - } - - m, _ := newTree(txHashes) - - return m -} - -//NewTree creates a new Merkle Tree using the content cs. -func newTree(txSlices [][32]byte) (*MerkleTree, error) { +//NewMerkleTree creates a new Merkle Tree using the content cs. +func NewMerkleTree(txSlices HashArray) (*MerkleTree, error) { root, leafs, err := buildWithContent(txSlices) if err != nil { return nil, err @@ -92,10 +41,22 @@ func newTree(txSlices [][32]byte) (*MerkleTree, error) { return t, nil } +//verifyNode walks down the tree until hitting a leaf, calculating the hash at each level +//and returning the resulting hash of Node n. +func (n *Node) verifyNode() [32]byte { + if n.leaf { + return n.Hash + } + leftHash := n.Left.verifyNode() + rightHash := n.Right.verifyNode() + concatHash := append(leftHash[:], rightHash[:]...) + return MTHash(concatHash) +} + //buildWithContent is a helper function that for a given set of Contents, generates a //corresponding tree and returns the root node, a list of leaf nodes, and a possible error. //Returns an error if cs contains no Contents. -func buildWithContent(txSlices [][32]byte) (*Node, []*Node, error) { +func buildWithContent(txSlices HashArray) (*Node, []*Node, error) { if len(txSlices) == 0 { return nil, nil, errors.New("Error: cannot construct tree with no content.") } @@ -129,7 +90,7 @@ func buildIntermediate(nl []*Node) *Node { n := &Node{ Left: nl[i], Right: nl[i+1], - Hash: sha3.Sum256(concatHash), + Hash: MTHash(concatHash), } nodes = append(nodes, n) nl[i].Parent = n @@ -174,8 +135,8 @@ func (m *MerkleTree) VerifyTree() bool { return false } -func GetLeaf(merkleTree *MerkleTree, leafHash [32]byte) *Node { - for _, leaf := range merkleTree.Leafs { +func (m *MerkleTree) GetLeaf(leafHash [32]byte) *Node { + for _, leaf := range m.Leafs { if leafHash == leaf.Hash { return leaf } @@ -183,6 +144,56 @@ func GetLeaf(merkleTree *MerkleTree, leafHash [32]byte) *Node { return nil } +func MTHash(data []byte) [32]byte { + return sha3.Sum256(data) +} + +func (m *MerkleTree) MerkleProof(leafHash [32]byte) (hashes [][33]byte, err error) { + leaf := m.GetLeaf(leafHash) + if leaf == nil { + return nil, errors.New(fmt.Sprintf("could not find leaf for hash %x", leafHash[0:8])) + } + currentNode := leaf + currentParent := leaf.Parent + for currentParent != nil { + left := currentParent.Left + right := currentParent.Right + var hash [33]byte + if currentNode.Hash == left.Hash { + copy(hash[0:1], []byte{'r'}) + copy(hash[1:33], right.Hash[:]) + hashes = append(hashes, hash) + } else if currentNode.Hash == right.Hash { + copy(hash[0:1], []byte{'l'}) + copy(hash[1:33], left.Hash[:]) + hashes = append(hashes, hash) + } else { + return nil, errors.New(fmt.Sprintf("could not find intermediate node to verify %x\n", leaf.Hash)) + } + currentNode = currentParent + currentParent = currentParent.Parent + } + return hashes, nil +} + +func (m *MerkleTree) VerifyMerkleProof(leafHash [32]byte, hashes [][33]byte) bool { + computedRootHash := leafHash + for i := 0; i < len(hashes); i++ { + var hash [32]byte + var leftOrRight [1]byte + copy(leftOrRight[:], hashes[i][0:1]) + copy(hash[:], hashes[i][1:33]) + + if leftOrRight == [1]byte{'l'} { + computedRootHash = MTHash(append(hash[:], computedRootHash[:]...)) + } else { + computedRootHash = MTHash(append(computedRootHash[:], hash[:]...)) + } + } + + return computedRootHash == m.MerkleRoot() +} + //VerifyContent indicates whether a given content is in the tree and the hashes are valid for that content. //Returns true if the expected Merkle Root is equivalent to the Merkle root calculated on the critical path //for a given content. Returns true if valid and false otherwise. diff --git a/protocol/merkletree_test.go b/protocol/merkletree_test.go index 3c3d6b9..48de95c 100644 --- a/protocol/merkletree_test.go +++ b/protocol/merkletree_test.go @@ -1,30 +1,23 @@ package protocol import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "testing" - "golang.org/x/crypto/sha3" + "math/rand" + "testing" + "time" ) func TestBuildMerkleTree3N(t *testing.T) { - var hashSlice [][32]byte - var tx *FundsTx - - //Generating a private key and prepare data - privA, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + var hashSlice HashArray for i := 0; i < 3; i++ { - tx, _ = ConstrFundsTx(0, 10, 1, uint32(i), [64]byte{'1'}, [64]byte{'2'}, privA, nil) - hashSlice = append(hashSlice, tx.Hash()) + var txHash [32]byte + rand.Read(txHash[:]) + hashSlice = append(hashSlice, txHash) } - b := Block{ - FundsTxData: hashSlice, - } + m, _ := NewMerkleTree(hashSlice) concat12 := append(hashSlice[0][:], hashSlice[1][:]...) hash12 := sha3.Sum256(concat12) @@ -35,7 +28,6 @@ func TestBuildMerkleTree3N(t *testing.T) { concat1233 := append(hash12[:], hash33[:]...) hash1233 := sha3.Sum256(concat1233) - m := BuildMerkleTree(&b) if hash1233 != m.MerkleRoot() { t.Errorf("Hashes don't match: %x != %x\n", hash1233, m.MerkleRoot()) } @@ -43,25 +35,19 @@ func TestBuildMerkleTree3N(t *testing.T) { func TestBuildMerkleTree2N(t *testing.T) { - var hashSlice [][32]byte - var tx *FundsTx - - //Generating a private key and prepare data - privA, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + var hashSlice HashArray for i := 0; i < 2; i++ { - tx, _ = ConstrFundsTx(0, 10, 1, uint32(i), [64]byte{'1'}, [64]byte{'2'}, privA, nil) - hashSlice = append(hashSlice, tx.Hash()) + var txHash [32]byte + rand.Read(txHash[:]) + hashSlice = append(hashSlice, txHash) } - b := Block{ - FundsTxData: hashSlice, - } + m, _ := NewMerkleTree(hashSlice) concat12 := append(hashSlice[0][:], hashSlice[1][:]...) hash12 := sha3.Sum256(concat12) - m := BuildMerkleTree(&b) if hash12 != m.MerkleRoot() { t.Errorf("Hashes don't match: %x != %x\n", hash12, m.MerkleRoot()) } @@ -70,20 +56,15 @@ func TestBuildMerkleTree2N(t *testing.T) { func TestBuildMerkleTree4N(t *testing.T) { - var hashSlice [][32]byte - var tx *FundsTx - - //Generating a private key and prepare data - privA, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + var hashSlice HashArray for i := 0; i < 4; i++ { - tx, _ = ConstrFundsTx(0, 10, 1, uint32(i), [64]byte{'1'}, [64]byte{'2'}, privA, nil) - hashSlice = append(hashSlice, tx.Hash()) + var txHash [32]byte + rand.Read(txHash[:]) + hashSlice = append(hashSlice, txHash) } - b := Block{ - FundsTxData: hashSlice, - } + m, _ := NewMerkleTree(hashSlice) concat12 := append(hashSlice[0][:], hashSlice[1][:]...) hash12 := sha3.Sum256(concat12) @@ -96,7 +77,6 @@ func TestBuildMerkleTree4N(t *testing.T) { concat1234 := append(hash12[:], hash34[:]...) hash1234 := sha3.Sum256(concat1234) - m := BuildMerkleTree(&b) if hash1234 != m.MerkleRoot() { t.Errorf("Hashes don't match: %x != %x\n", hash1234, m.MerkleRoot()) } @@ -105,20 +85,15 @@ func TestBuildMerkleTree4N(t *testing.T) { func TestBuildMerkleTree6N(t *testing.T) { - var hashSlice [][32]byte - var tx *FundsTx - - //Generating a private key and prepare data - privA, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + var hashSlice HashArray for i := 0; i < 6; i++ { - tx, _ = ConstrFundsTx(0, 10, 1, uint32(i), [64]byte{'1'}, [64]byte{'2'}, privA, nil) - hashSlice = append(hashSlice, tx.Hash()) + var txHash [32]byte + rand.Read(txHash[:]) + hashSlice = append(hashSlice, txHash) } - b := Block{ - FundsTxData: hashSlice, - } + m, _ := NewMerkleTree(hashSlice) concat12 := append(hashSlice[0][:], hashSlice[1][:]...) hash12 := sha3.Sum256(concat12) @@ -139,7 +114,6 @@ func TestBuildMerkleTree6N(t *testing.T) { concat123456 := append(hash1234[:], hash56[:]...) hash123456 := sha3.Sum256(concat123456) - m := BuildMerkleTree(&b) if hash123456 != m.MerkleRoot() { t.Errorf("Hashes don't match: %x != %x\n", hash123456, m.MerkleRoot()) } @@ -148,20 +122,15 @@ func TestBuildMerkleTree6N(t *testing.T) { func TestBuildMerkleTree8N(t *testing.T) { - var hashSlice [][32]byte - var tx *FundsTx - - //Generating a private key and prepare data - privA, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + var hashSlice HashArray for i := 0; i < 8; i++ { - tx, _ = ConstrFundsTx(0, 10, 1, uint32(i), [64]byte{'1'}, [64]byte{'2'}, privA, nil) - hashSlice = append(hashSlice, tx.Hash()) + var txHash [32]byte + rand.Read(txHash[:]) + hashSlice = append(hashSlice, txHash) } - b := Block{ - FundsTxData: hashSlice, - } + m, _ := NewMerkleTree(hashSlice) concat12 := append(hashSlice[0][:], hashSlice[1][:]...) hash12 := sha3.Sum256(concat12) @@ -188,7 +157,6 @@ func TestBuildMerkleTree8N(t *testing.T) { concat12345678 := append(hash1234[:], hash5678[:]...) hash12345678 := sha3.Sum256(concat12345678) - m := BuildMerkleTree(&b) if hash12345678 != m.MerkleRoot() { t.Errorf("Hashes don't match: %x != %x\n", hash12345678, m.MerkleRoot()) } @@ -197,20 +165,15 @@ func TestBuildMerkleTree8N(t *testing.T) { func TestBuildMerkleTree10N(t *testing.T) { - var hashSlice [][32]byte - var tx *FundsTx - - //Generating a private key and prepare data - privA, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + var hashSlice HashArray for i := 0; i < 10; i++ { - tx, _ = ConstrFundsTx(0, 10, 1, uint32(i), [64]byte{'1'}, [64]byte{'2'}, privA, nil) - hashSlice = append(hashSlice, tx.Hash()) + var txHash [32]byte + rand.Read(txHash[:]) + hashSlice = append(hashSlice, txHash) } - b := Block{ - FundsTxData: hashSlice, - } + m, _ := NewMerkleTree(hashSlice) concat12 := append(hashSlice[0][:], hashSlice[1][:]...) hash12 := sha3.Sum256(concat12) @@ -245,7 +208,6 @@ func TestBuildMerkleTree10N(t *testing.T) { concat12345678910 := append(hash12345678[:], hash910[:]...) hash12345678910 := sha3.Sum256(concat12345678910) - m := BuildMerkleTree(&b) if hash12345678910 != m.MerkleRoot() { t.Errorf("Hashes don't match: %x != %x\n", hash12345678910, m.MerkleRoot()) } @@ -254,20 +216,15 @@ func TestBuildMerkleTree10N(t *testing.T) { func TestBuildMerkleTree11N(t *testing.T) { - var hashSlice [][32]byte - var tx *FundsTx - - //Generating a private key and prepare data - privA, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + var hashSlice HashArray for i := 0; i < 11; i++ { - tx, _ = ConstrFundsTx(0, 10, 1, uint32(i), [64]byte{'1'}, [64]byte{'2'}, privA, nil) - hashSlice = append(hashSlice, tx.Hash()) + var txHash [32]byte + rand.Read(txHash[:]) + hashSlice = append(hashSlice, txHash) } - b := Block{ - FundsTxData: hashSlice, - } + m, _ := NewMerkleTree(hashSlice) concat12 := append(hashSlice[0][:], hashSlice[1][:]...) hash12 := sha3.Sum256(concat12) @@ -308,7 +265,6 @@ func TestBuildMerkleTree11N(t *testing.T) { concat123456789101111 := append(hash12345678[:], hash9101111[:]...) hash123456789101111 := sha3.Sum256(concat123456789101111) - m := BuildMerkleTree(&b) if hash123456789101111 != m.MerkleRoot() { t.Errorf("Hashes don't match: %x != %x\n", hash123456789101111, m.MerkleRoot()) } @@ -317,19 +273,12 @@ func TestBuildMerkleTree11N(t *testing.T) { func TestGetIntermediate(t *testing.T) { - var hashSlice [][32]byte - var tx *FundsTx - - //Generating a private key and prepare data - privA, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + var hashSlice HashArray for i := 0; i < 11; i++ { - tx, _ = ConstrFundsTx(0, 10, 1, uint32(i), [64]byte{'1'}, [64]byte{'2'}, privA, nil) - hashSlice = append(hashSlice, tx.Hash()) - } - - b := Block{ - FundsTxData: hashSlice, + var txHash [32]byte + rand.Read(txHash[:]) + hashSlice = append(hashSlice, txHash) } concat12 := append(hashSlice[0][:], hashSlice[1][:]...) @@ -368,9 +317,9 @@ func TestGetIntermediate(t *testing.T) { concat12345678 := append(hash1234[:], hash5678[:]...) hash12345678 := sha3.Sum256(concat12345678) - merkleTree := BuildMerkleTree(&b) + m, _ := NewMerkleTree(hashSlice) - intermediates, _ := GetIntermediate(GetLeaf(merkleTree, hashSlice[9])) + intermediates, _ := GetIntermediate(m.GetLeaf(hashSlice[9])) if intermediates[0].Hash != hashSlice[8] { t.Errorf("Hashes don't match: %x != %x\n", intermediates[0].Hash, hashSlice[8]) @@ -392,3 +341,119 @@ func TestGetIntermediate(t *testing.T) { t.Errorf("Hashes don't match: %x != %x\n", intermediates[4].Hash, hash12345678) } } + + + +func TestMerkleProof(t *testing.T) { + randVal := rand.New(rand.NewSource(time.Now().Unix())) + + var hash1, hash2, hash3, hash4 [32]byte + + randVal.Read(hash1[:]) + randVal.Read(hash2[:]) + randVal.Read(hash3[:]) + randVal.Read(hash4[:]) + + var hashSlice HashArray + hashSlice = append(hashSlice, hash1, hash2, hash3, hash4) + + m, _ := NewMerkleTree(hashSlice) + + hash12 := MTHash(append(hash1[:], hash2[:]...)) + hash34 := MTHash(append(hash3[:], hash4[:]...)) + hash1234 := MTHash(append(hash12[:], hash34[:]...)) + if hash1234 != m.MerkleRoot() { + t.Errorf("Root hashes don't match: %x != %x\n", hash1234, m.MerkleRoot()) + } + + hashes, err := m.MerkleProof(hash1) + if err != nil { + t.Error(err) + } + + if len(hashes) != 2 { + t.Errorf("Merkle proof returns invalid amount of hashes") + } + + var mhash [32]byte + var leftOrRight [1]byte + copy(leftOrRight[:], hashes[0][0:1]) + copy(mhash[:], hashes[0][1:33]) + + if leftOrRight != [1]byte{'r'} { + t.Errorf("invalid left/right byte: is left but should be right") + } + + if hash2 != mhash { + t.Errorf("invalid Merkle proof hash at index 0") + } + + copy(leftOrRight[:], hashes[1][0:1]) + copy(mhash[:], hashes[1][1:33]) + + if leftOrRight != [1]byte{'r'} { + t.Errorf("invalid left/right byte: is left but should be right") + } + + if hash34 != mhash { + t.Errorf("invalid Merkle proof hash at index 1") + } +} + + + +func TestMerkleProofWithVerification(t *testing.T) { + randVal := rand.New(rand.NewSource(time.Now().Unix())) + var hashSlice HashArray + nofHashes := int(randVal.Uint32() % 1000) + 1 + for i := 0; i < nofHashes; i++ { + var txHash [32]byte + randVal.Read(txHash[:]) + hashSlice = append(hashSlice, txHash) + } + + m, _ := NewMerkleTree(hashSlice) + + randomIndex := randVal.Uint32() % uint32(nofHashes) + leafHash := hashSlice[randomIndex] + hashes, err := m.MerkleProof(leafHash) + if err != nil { + t.Error(err) + } + + if !m.VerifyMerkleProof(leafHash, hashes) { + t.Errorf("Merkle proof verification failed for hash %x", leafHash) + } +} + +func TestVerifyMerkleProof(t *testing.T) { + randVal := rand.New(rand.NewSource(time.Now().Unix())) + + var hash1, hash2, hash3, hash4 [32]byte + + randVal.Read(hash1[:]) + randVal.Read(hash2[:]) + randVal.Read(hash3[:]) + randVal.Read(hash4[:]) + + var hashSlice HashArray + hashSlice = append(hashSlice, hash1, hash2, hash3, hash4) + + m, _ := NewMerkleTree(hashSlice) + + hash12 := MTHash(append(hash1[:], hash2[:]...)) + hash34 := MTHash(append(hash3[:], hash4[:]...)) + hash1234 := MTHash(append(hash12[:], hash34[:]...)) + if hash1234 != m.MerkleRoot() { + t.Errorf("Root hashes don't match: %x != %x\n", hash1234, m.MerkleRoot()) + } + + hashes, err := m.MerkleProof(hash1) + if err != nil { + t.Error(err) + } + + if !m.VerifyMerkleProof(hash1, hashes) { + t.Errorf("Merkle proof verification failed for hash %x", hash1) + } +} \ No newline at end of file diff --git a/protocol/txbucket.go b/protocol/txbucket.go new file mode 100644 index 0000000..08ec761 --- /dev/null +++ b/protocol/txbucket.go @@ -0,0 +1,160 @@ +package protocol + +import ( + "bytes" + "encoding/gob" + "fmt" + "log" + "sort" +) + +type TxBucket struct { + Address [64]byte + RelativeBalance int64 + + merkleRoot HashType // should not be accessed directly, instead, call CalculateMerkleRoot() + + Transactions []*FundsTx // won't be serialized +} + +func NewTxBucket(address [64]byte) *TxBucket { + return &TxBucket{ + Address: address, + } +} + +func (bucket *TxBucket) AddFundsTx(tx *FundsTx) { + if tx.From == bucket.Address { + // Bucket owner is sender of the transaction, must pay the amount and the fee + bucket.RelativeBalance -= int64(tx.Amount + tx.Fee) + } else if tx.To == bucket.Address { + // Bucket owner is receiver of the transaction, must not pay for the fee + bucket.RelativeBalance += int64(tx.Amount) + } else { + return + } + + bucket.Transactions = append(bucket.Transactions, tx) +} + +func (bucket *TxBucket) CalculateMerkleRoot() HashType { + emptyHash := HashType{} + if bucket.merkleRoot == emptyHash { + merkleTree := bucket.buildMerkleTree() + bucket.merkleRoot = merkleTree.MerkleRoot() + } + return bucket.merkleRoot +} + +func (bucket *TxBucket) buildMerkleTree() *MerkleTree { + if bucket == nil { + return nil + } + + //Merkle root for no transactions is 0 hash + if len(bucket.Transactions) == 0 { + return nil + } + + var txHashes HashArray + for _, tx := range bucket.Transactions { + txHashes = append(txHashes, tx.Hash()) + } + + m, _ := NewMerkleTree(txHashes) + + return m +} + +func (bucket *TxBucket) Hash() HashType { + if bucket == nil { + return HashType{} + } + + bucketHash := struct { + address [64]byte + amount int64 + merkleRoot HashType + }{ + bucket.Address, + bucket.RelativeBalance, + bucket.CalculateMerkleRoot(), + } + + return SerializeHashContent(bucketHash) +} + +func (bucket *TxBucket) Encode() []byte { + if bucket == nil { + return nil + } + + encoded := TxBucket{ + Address: bucket.Address, + RelativeBalance: bucket.RelativeBalance, + merkleRoot: bucket.CalculateMerkleRoot(), + } + + buffer := new(bytes.Buffer) + gob.NewEncoder(buffer).Encode(encoded) + return buffer.Bytes() +} + +func (bucket *TxBucket) Decode(encoded []byte) (b *TxBucket) { + if encoded == nil { + return nil + } + + var decoded TxBucket + buffer := bytes.NewBuffer(encoded) + decoder := gob.NewDecoder(buffer) + decoder.Decode(&decoded) + return &decoded +} + +func (bucket TxBucket) String() string { + hash := bucket.Hash() + merkleRoot := bucket.CalculateMerkleRoot() + + return fmt.Sprintf("\nHash: %x\n" + + "Address: %x\n" + + "Relative Balance: %v\n"+ + "Merkle Root: %x\n", + hash[0:8], + bucket.Address[0:8], + bucket.RelativeBalance, + merkleRoot[0:8], + ) +} + +type txBucketMap map[AddressType]*TxBucket + +func NewTxBucketMap() txBucketMap { + return make(txBucketMap) +} + +func (m txBucketMap) Sort() txBucketMap { + var buckets []*TxBucket + for _, bucket := range m { + buckets = append(buckets, bucket) + } + + sort.Slice(buckets, func(i, j int) bool { + switch bytes.Compare(buckets[i].Address[:], buckets[j].Address[:]) { + case -1: + return true + case 0, 1: + return false + default: + log.Panic("not fail-able with `bytes.Comparable` bounded [-1, 1].") + return false + } + }) + + sortedMap := NewTxBucketMap() + for _, bucket := range buckets { + sortedMap[bucket.Address] = bucket + } + + return sortedMap +} \ No newline at end of file diff --git a/protocol/txbucket_test.go b/protocol/txbucket_test.go new file mode 100644 index 0000000..e124322 --- /dev/null +++ b/protocol/txbucket_test.go @@ -0,0 +1,93 @@ +package protocol + +import ( + "math/rand" + "reflect" + "testing" +) + +func TestBucketCreation(t *testing.T) { + var address [64]byte + rand.Read(address[:]) + bucket := NewTxBucket(address) + + if !reflect.DeepEqual(bucket.Address, address) { + t.Errorf("BucketAddress does not match the given one: %x vs. %x", bucket.Address, address) + } +} + +func TestBucketHash(t *testing.T) { + var address [64]byte + rand.Read(address[:]) + bucket := NewTxBucket(address) + + hash1 := bucket.Hash() + + if !reflect.DeepEqual(hash1, bucket.Hash()) { + t.Errorf("Bucket hashing failed!") + } + + rand.Read(address[:]) + bucket.Address = address + + hash2 := bucket.Hash() + + if reflect.DeepEqual(hash1, hash2) { + t.Errorf("Bucket hashing failed!") + } + + if !reflect.DeepEqual(hash2, bucket.Hash()) { + t.Errorf("Bucket hashing failed!") + } +} + +func TestBucketSerialization(t *testing.T) { + var bucket TxBucket + + rand.Read(bucket.Address[:]) + bucket.RelativeBalance = rand.Int63() + + encodedBucket := bucket.Encode() + + var compareBucket TxBucket + compareBucket = *compareBucket.Decode(encodedBucket) + if !reflect.DeepEqual(bucket, compareBucket) { + t.Error("Bucket encoding/decoding failed!") + } +} + +func TestBucketAddFundsTx(t *testing.T) { + bucket := NewTxBucket(accA.Address) + + amount := uint64(100) + fee := uint64(1) + + tx := NewSimpleFundsTx(amount, fee, uint32(0), accA.Address, accB.Address) + bucket.AddFundsTx(tx) + + expectedAmount := int64(amount + fee) * -1 + if bucket.RelativeBalance != expectedAmount { + t.Errorf("invalid bucket amount, is %v but should be %v", bucket.RelativeBalance, expectedAmount) + } + + amount = uint64(300) + + tx = NewSimpleFundsTx(amount, fee, uint32(0), accB.Address, accA.Address) + bucket.AddFundsTx(tx) + + expectedAmount = expectedAmount + int64(amount) + if bucket.RelativeBalance != expectedAmount { + t.Errorf("invalid bucket amount, is %v but should be %v", bucket.RelativeBalance, expectedAmount) + } + + var randomAddressA, randomAddressB [64]byte + rand.Read(randomAddressA[:]) + rand.Read(randomAddressB[:]) + + tx = NewSimpleFundsTx(amount, fee, uint32(0), randomAddressA, randomAddressB) + bucket.AddFundsTx(tx) + + if bucket.RelativeBalance != expectedAmount { + t.Errorf("invalid bucket amount, is %v but should be %v", bucket.RelativeBalance, expectedAmount) + } +} \ No newline at end of file diff --git a/protocol/types.go b/protocol/types.go new file mode 100644 index 0000000..dda3cb9 --- /dev/null +++ b/protocol/types.go @@ -0,0 +1,33 @@ +package protocol + +import ( + "bytes" + "log" +) + +type ByteArray []byte +type AddressType [64]byte +type HashType [32]byte +type HashArray []HashType + +func (h HashArray) Len() int { + return len(h) +} + +func (h HashArray) Less(i, j int) bool { + switch bytes.Compare(h[i][:], h[j][:]) { + case -1: + return true + case 0, 1: + return false + default: + log.Panic("not fail-able with `bytes.Comparable` bounded [-1, 1].") + return false + } +} + +func (h HashArray) Swap(i, j int) { + h[j], h[i] = h[i], h[j] +} + + diff --git a/storage/storage.go b/storage/storage.go index a85fddd..0148028 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -56,7 +56,7 @@ func Init(dbname string, bootstrapIpport string) error { } for _, bucket := range Buckets { - err = createBucket(bucket) + err = CreateBucket(bucket, db) if err != nil { return err } @@ -65,9 +65,9 @@ func Init(dbname string, bootstrapIpport string) error { return nil } -func createBucket(bucketName string) (err error) { +func CreateBucket(bucketName string, db *bolt.DB) (err error) { return db.Update(func(tx *bolt.Tx) error { - _, err = tx.CreateBucket([]byte(bucketName)) + _, err = tx.CreateBucketIfNotExists([]byte(bucketName)) if err != nil { return fmt.Errorf(ERROR_MSG + " %s", err) } diff --git a/storage/storage_test.go b/storage/storage_test.go index 10936af..955e3a9 100644 --- a/storage/storage_test.go +++ b/storage/storage_test.go @@ -25,7 +25,7 @@ func TestReadWriteDeleteTx(t *testing.T) { loopMax := testsize for i := 0; i < loopMax; i++ { - tx, _ := protocol.ConstrFundsTx(0x01, rand.Uint64()%100000+1, rand.Uint64()%10+1, uint32(i), accA.Address, accB.Address, &PrivKeyA, nil) + tx, _ := protocol.NewSignedFundsTx(0x01, rand.Uint64()%100000+1, rand.Uint64()%10+1, uint32(i), accA.Address, accB.Address, &PrivKeyA, nil) WriteOpenTx(tx) hashFundsSlice = append(hashFundsSlice, tx) } diff --git a/storage/utils.go b/storage/utils.go index f602c0c..6a05f11 100644 --- a/storage/utils.go +++ b/storage/utils.go @@ -19,6 +19,7 @@ func IsRootKey(pubKey [64]byte) bool { func GetTxPubKeys(block *protocol.Block) (txPubKeys [][64]byte) { txPubKeys = GetContractTxPubKeys(block.ContractTxData) txPubKeys = append(txPubKeys, GetFundsTxPubKeys(block.FundsTxData)...) + txPubKeys = append(txPubKeys, block.Beneficiary) return txPubKeys }