Skip to content
Snippets Groups Projects
Commit 844f6571 authored by Hans Moog's avatar Hans Moog
Browse files

Feat: client package / value bundle validation tests

parent e3104920
No related branches found
No related tags found
No related merge requests found
package client
import (
"github.com/iotaledger/iota.go/consts"
"github.com/iotaledger/iota.go/trinary"
)
type Address struct {
trytes trinary.Trytes
securityLevel consts.SecurityLevel
privateKey trinary.Trits
}
func NewAddress(trytes trinary.Trytes) *Address {
return &Address{
trytes: trytes,
}
}
func (address *Address) GetTrytes() trinary.Trytes {
return address.trytes
}
func (address *Address) GetSecurityLevel() consts.SecurityLevel {
return address.securityLevel
}
func (address *Address) GetPrivateKey() trinary.Trits {
return address.privateKey
}
package client
import (
"github.com/iotaledger/goshimmer/packages/curl"
"github.com/iotaledger/goshimmer/packages/model/value_transaction"
"github.com/iotaledger/iota.go/trinary"
)
type Bundle struct {
essenceHash trinary.Trytes
transactions []*value_transaction.ValueTransaction
}
func (bundle *Bundle) GetEssenceHash() trinary.Trytes {
return bundle.essenceHash
}
func (bundle *Bundle) GetTransactions() []*value_transaction.ValueTransaction {
return bundle.transactions
}
func CalculateBundleHash(transactions []*value_transaction.ValueTransaction) trinary.Trytes {
var lastInputAddress trinary.Trytes
var concatenatedBundleEssences = make(trinary.Trits, len(transactions)*value_transaction.BUNDLE_ESSENCE_SIZE)
for i, bundleTransaction := range transactions {
if bundleTransaction.GetValue() <= 0 {
lastInputAddress = bundleTransaction.GetAddress()
}
copy(concatenatedBundleEssences[value_transaction.BUNDLE_ESSENCE_SIZE*i:value_transaction.BUNDLE_ESSENCE_SIZE*(i+1)], bundleTransaction.GetBundleEssence(lastInputAddress != bundleTransaction.GetAddress()))
}
var bundleHash = make(trinary.Trits, 243)
hasher := curl.NewCurl(243, 81)
hasher.Absorb(concatenatedBundleEssences, 0, len(concatenatedBundleEssences))
hasher.Squeeze(bundleHash, 0, 243)
return trinary.MustTritsToTrytes(bundleHash)
}
package client
import (
"github.com/iotaledger/goshimmer/packages/model/value_transaction"
"github.com/iotaledger/iota.go/consts"
"github.com/iotaledger/iota.go/converter"
"github.com/iotaledger/iota.go/signing"
"github.com/iotaledger/iota.go/trinary"
)
type BundleFactory struct {
inputs []bundleFactoryInputEntry
outputs []bundleFactoryOutputEntry
}
func NewBundleFactory() *BundleFactory {
return &BundleFactory{
inputs: make([]bundleFactoryInputEntry, 0),
outputs: make([]bundleFactoryOutputEntry, 0),
}
}
func (bundleFactory *BundleFactory) AddInput(address *Address, value int64) {
bundleFactory.inputs = append(bundleFactory.inputs, bundleFactoryInputEntry{
address: address,
value: value,
})
}
func (bundleFactory *BundleFactory) AddOutput(address *Address, value int64, message ...string) {
if len(message) >= 1 {
messageTrytes, err := converter.ASCIIToTrytes(message[0])
if err != nil {
panic(err)
}
bundleFactory.outputs = append(bundleFactory.outputs, bundleFactoryOutputEntry{
address: address,
value: value,
message: trinary.Pad(messageTrytes, value_transaction.SIGNATURE_MESSAGE_FRAGMENT_SIZE),
})
} else {
bundleFactory.outputs = append(bundleFactory.outputs, bundleFactoryOutputEntry{
address: address,
value: value,
})
}
}
func (bundleFactory *BundleFactory) GenerateBundle(branchTransactionHash trinary.Trytes, trunkTransactionHash trinary.Trytes) *Bundle {
transactions := bundleFactory.generateTransactions()
bundleHash := bundleFactory.signTransactions(transactions)
bundleFactory.connectTransactions(transactions, branchTransactionHash, trunkTransactionHash)
return &Bundle{
essenceHash: bundleHash,
transactions: transactions,
}
}
func (bundleFactory *BundleFactory) generateTransactions() []*value_transaction.ValueTransaction {
transactions := make([]*value_transaction.ValueTransaction, 0)
for _, input := range bundleFactory.inputs {
transaction := value_transaction.New()
transaction.SetValue(input.value)
transaction.SetAddress(input.address.trytes)
transactions = append(transactions, transaction)
for i := 1; i < int(input.address.securityLevel); i++ {
transaction := value_transaction.New()
transaction.SetValue(0)
transaction.SetAddress(input.address.trytes)
transactions = append(transactions, transaction)
}
}
for _, output := range bundleFactory.outputs {
transaction := value_transaction.New()
transaction.SetValue(output.value)
transaction.SetAddress(output.address.trytes)
if len(output.message) != 0 {
transaction.SetSignatureMessageFragment(output.message)
}
transactions = append(transactions, transaction)
}
transactions[0].SetHead(true)
transactions[len(transactions)-1].SetTail(true)
return transactions
}
func (bundleFactory *BundleFactory) signTransactions(transactions []*value_transaction.ValueTransaction) trinary.Trytes {
bundleHash := CalculateBundleHash(transactions)
normalizedBundleHash := signing.NormalizedBundleHash(bundleHash)
signedTransactions := 0
for _, input := range bundleFactory.inputs {
securityLevel := input.address.securityLevel
privateKey := input.address.privateKey
for i := 0; i < int(securityLevel); i++ {
signedFragTrits, _ := signing.SignatureFragment(
normalizedBundleHash[i*consts.HashTrytesSize/3:(i+1)*consts.HashTrytesSize/3],
privateKey[i*consts.KeyFragmentLength:(i+1)*consts.KeyFragmentLength],
)
transactions[signedTransactions].SetSignatureMessageFragment(trinary.MustTritsToTrytes(signedFragTrits))
signedTransactions++
}
}
return bundleHash
}
func (bundleFactory *BundleFactory) connectTransactions(transactions []*value_transaction.ValueTransaction, branchTransactionHash trinary.Trytes, trunkTransactionHash trinary.Trytes) {
transactionCount := len(transactions)
transactions[transactionCount-1].SetBranchTransactionHash(branchTransactionHash)
transactions[transactionCount-1].SetTrunkTransactionHash(trunkTransactionHash)
for i := transactionCount - 2; i >= 0; i-- {
transactions[i].SetBranchTransactionHash(branchTransactionHash)
transactions[i].SetTrunkTransactionHash(transactions[i+1].GetHash())
}
}
type bundleFactoryInputEntry struct {
address *Address
value int64
}
type bundleFactoryOutputEntry struct {
address *Address
value int64
message trinary.Trytes
}
package client
package client
import (
"github.com/iotaledger/iota.go/address"
"github.com/iotaledger/iota.go/consts"
"github.com/iotaledger/iota.go/signing"
"github.com/iotaledger/iota.go/trinary"
)
type Seed struct {
trytes trinary.Trytes
securityLevel consts.SecurityLevel
subSeeds map[uint64]trinary.Trits
}
func NewSeed(trytes trinary.Trytes, securityLevel consts.SecurityLevel) *Seed {
return &Seed{
trytes: trytes,
securityLevel: securityLevel,
subSeeds: make(map[uint64]trinary.Trits),
}
}
func (seed *Seed) GetAddress(index uint64) *Address {
addressTrytes, err := address.GenerateAddress(seed.trytes, index, seed.securityLevel)
if err != nil {
panic(err)
}
privateKey, err := signing.Key(seed.GetSubSeed(index), seed.securityLevel)
if err != nil {
panic(err)
}
return &Address{
trytes: addressTrytes,
securityLevel: seed.securityLevel,
privateKey: privateKey,
}
}
func (seed *Seed) GetSubSeed(index uint64) trinary.Trits {
subSeed, subSeedExists := seed.subSeeds[index]
if !subSeedExists {
generatedSubSeed, err := signing.Subseed(seed.trytes, index)
if err != nil {
panic(err)
}
subSeed = generatedSubSeed
seed.subSeeds[index] = subSeed
}
return subSeed
}
package client
import (
"github.com/iotaledger/iota.go/consts"
"github.com/iotaledger/iota.go/trinary"
)
type ValueBundleFactory struct {
seed trinary.Trytes
securityLevel consts.SecurityLevel
seedInputs map[uint64]uint64
seedOutputs map[uint64]uint64
}
func New(seed trinary.Trytes, securityLevel consts.SecurityLevel) *ValueBundleFactory {
return &ValueBundleFactory{
seed: seed,
securityLevel: securityLevel,
seedInputs: make(map[uint64]uint64),
seedOutputs: make(map[uint64]uint64),
}
}
func (factory *ValueBundleFactory) AddInput(addressIndex uint64, value uint64) {
factory.seedInputs[addressIndex] = value
}
package curl
import (
"fmt"
"strconv"
"github.com/iotaledger/goshimmer/packages/batchworkerpool"
......@@ -40,7 +39,7 @@ func (this *BatchHasher) processHashes(tasks []batchworkerpool.Task) {
}
bcTrits, err := multiplexer.Extract()
if err != nil {
fmt.Println(err)
panic(err)
}
// calculate the hash
......
......@@ -197,22 +197,20 @@ func (this *ValueTransaction) SetTimestamp(timestamp uint) bool {
return false
}
func (this *ValueTransaction) GetBundleEssence() (result trinary.Trits) {
this.addressMutex.RLock()
this.valueMutex.RLock()
func (this *ValueTransaction) GetBundleEssence(includeSignatureMessageFragment bool) (result trinary.Trits) {
this.signatureMessageFragmentMutex.RLock()
result = make(trinary.Trits, BUNDLE_ESSENCE_SIZE)
this.addressMutex.RLock()
copy(result[0:], this.trits[ADDRESS_OFFSET:VALUE_END])
this.addressMutex.RUnlock()
if this.GetValue() >= 0 {
if includeSignatureMessageFragment {
copy(result[VALUE_END:], this.trits[SIGNATURE_MESSAGE_FRAGMENT_OFFSET:SIGNATURE_MESSAGE_FRAGMENT_END])
}
this.signatureMessageFragmentMutex.RUnlock()
this.valueMutex.RUnlock()
this.addressMutex.RUnlock()
return
}
......
package bundleprocessor
import (
"fmt"
"github.com/iotaledger/goshimmer/packages/errors"
"github.com/iotaledger/goshimmer/packages/model/bundle"
"github.com/iotaledger/goshimmer/packages/model/transactionmetadata"
......@@ -11,16 +13,18 @@ import (
)
var workerPool = workerpool.New(func(task workerpool.Task) {
if _, err := ProcessSolidBundleHead(task.Param(0).(*value_transaction.ValueTransaction)); err != nil {
if err := ProcessSolidBundleHead(task.Param(0).(*value_transaction.ValueTransaction)); err != nil {
Events.Error.Trigger(err)
}
task.Return(nil)
}, workerpool.WorkerCount(WORKER_COUNT), workerpool.QueueSize(2*WORKER_COUNT))
const WORKER_COUNT = 10000
func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction) (*bundle.Bundle, errors.IdentifiableError) {
func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction) errors.IdentifiableError {
// only process the bundle if we didn't process it, yet
return tangle.GetBundle(headTransaction.GetHash(), func(headTransactionHash trinary.Trytes) (*bundle.Bundle, errors.IdentifiableError) {
_, err := tangle.GetBundle(headTransaction.GetHash(), func(headTransactionHash trinary.Trytes) (*bundle.Bundle, errors.IdentifiableError) {
// abort if bundle syntax is wrong
if !headTransaction.IsHead() {
return nil, ErrProcessBundleFailed.Derive(errors.New("invalid parameter"), "transaction needs to be head of bundle")
......@@ -53,7 +57,7 @@ func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction)
currentTransactionMetadata.SetBundleHeadHash(headTransactionHash)
// update value bundle flag
if !newBundle.IsValueBundle() && currentTransaction.GetValue() != 0 {
if !newBundle.IsValueBundle() && currentTransaction.GetValue() < 0 {
newBundle.SetValueBundle(true)
}
......@@ -75,11 +79,16 @@ func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction)
// try to iterate to next turn
if nextTransaction, err := tangle.GetTransaction(currentTransaction.GetTrunkTransactionHash()); err != nil {
return nil, ErrProcessBundleFailed.Derive(err, "failed to retrieve trunk while processing bundle")
} else if nextTransaction == nil {
fmt.Println(ErrProcessBundleFailed.Derive(errors.New("missing transaction "+currentTransaction.GetTrunkTransactionHash()), "failed to retrieve trunk while processing bundle"))
return nil, ErrProcessBundleFailed.Derive(err, "failed to retrieve trunk while processing bundle")
} else {
currentTransaction = nextTransaction
}
}
})
return err
}
func mapTransactionsToTransactionHashes(transactions []*value_transaction.ValueTransaction) (result []trinary.Trytes) {
......
package bundleprocessor
import (
"fmt"
"sync"
"testing"
"github.com/iotaledger/goshimmer/packages/node"
"github.com/iotaledger/goshimmer/packages/events"
"github.com/iotaledger/goshimmer/packages/model/bundle"
"github.com/iotaledger/goshimmer/packages/model/value_transaction"
"github.com/iotaledger/goshimmer/plugins/tipselection"
"github.com/iotaledger/goshimmer/packages/client"
"github.com/iotaledger/goshimmer/packages/node"
"github.com/iotaledger/goshimmer/plugins/tangle"
"github.com/iotaledger/iota.go/trinary"
"github.com/iotaledger/iota.go/consts"
"github.com/magiconair/properties/assert"
)
func TestProcessSolidBundleHead(t *testing.T) {
var seed = client.NewSeed("YFHQWAUPCXC9S9DSHP9NDF9RLNPMZVCMSJKUKQP9SWUSUCPRQXCMDVDVZ9SHHESHIQNCXWBJF9UJSWE9Z", consts.SecurityLevelMedium)
func TestValidateSignatures(t *testing.T) {
bundleFactory := client.NewBundleFactory()
bundleFactory.AddInput(seed.GetAddress(0), -400)
bundleFactory.AddOutput(seed.GetAddress(1), 400, "Testmessage")
bundleFactory.AddOutput(client.NewAddress("SJKUKQP9SWUSUCPRQXCMDVDVZ9SHHESHIQNCXWBJF9UJSWE9ZYFHQWAUPCXC9S9DSHP9NDF9RLNPMZVCM"), 400, "Testmessage")
generatedBundle := bundleFactory.GenerateBundle(tipselection.GetRandomTip(), tipselection.GetRandomTip())
successfull, err := ValidateSignatures(generatedBundle.GetEssenceHash(), generatedBundle.GetTransactions())
if err != nil {
t.Error(err)
}
assert.Equal(t, successfull, true, "validation failed")
}
func TestProcessSolidBundleHead_Data(t *testing.T) {
// show all error messages for tests
*node.LOG_LEVEL.Value = node.LOG_LEVEL_DEBUG
*node.LOG_LEVEL.Value = node.LOG_LEVEL_FAILURE
// start a test node
node.Start(tangle.PLUGIN, PLUGIN)
tx := value_transaction.New()
tx.SetTail(true)
tx.SetValue(3)
bundleFactory := client.NewBundleFactory()
bundleFactory.AddOutput(seed.GetAddress(1), 400, "Testmessage")
bundleFactory.AddOutput(client.NewAddress("SJKUKQP9SWUSUCPRQXCMDVDVZ9SHHESHIQNCXWBJF9UJSWE9ZYFHQWAUPCXC9S9DSHP9NDF9RLNPMZVCM"), 400, "Testmessage")
tx1 := value_transaction.New()
tx1.SetTrunkTransactionHash(tx.GetHash())
tx1.SetHead(true)
generatedBundle := bundleFactory.GenerateBundle(tipselection.GetRandomTip(), tipselection.GetRandomTip())
tangle.StoreTransaction(tx)
tangle.StoreTransaction(tx1)
for _, transaction := range generatedBundle.GetTransactions() {
tangle.StoreTransaction(transaction)
}
Events.BundleSolid.Attach(events.NewClosure(func(bundle *bundle.Bundle, transactions []*value_transaction.ValueTransaction) {
fmt.Println("IT HAPPENED")
fmt.Println(bundle.GetHash())
fmt.Println(bundle.GetBundleEssenceHash())
}))
var wg sync.WaitGroup
result, err := ProcessSolidBundleHead(tx1)
if err != nil {
testResults := events.NewClosure(func(bundle *bundle.Bundle, transactions []*value_transaction.ValueTransaction) {
assert.Equal(t, bundle.GetHash(), generatedBundle.GetTransactions()[0].GetHash(), "invalid bundle hash")
assert.Equal(t, bundle.IsValueBundle(), false, "invalid value bundle status")
wg.Done()
})
Events.BundleSolid.Attach(testResults)
wg.Add(1)
if err := ProcessSolidBundleHead(generatedBundle.GetTransactions()[0]); err != nil {
t.Error(err)
} else {
assert.Equal(t, result.GetHash(), trinary.Trytes("UFWJYEWKMEQDNSQUCUWBGOFRHVBGHVVYEZCLCGRDTRQSMAFALTIPMJEEYFDPMQCNJWLXUWFMBZGHQRO99"), "invalid bundle hash")
assert.Equal(t, result.IsValueBundle(), true, "invalid value bundle status")
}
wg.Wait()
Events.BundleSolid.Detach(testResults)
// shutdown test node
node.Shutdown()
}
func TestProcessSolidBundleHead_Value(t *testing.T) {
// show all error messages for tests
*node.LOG_LEVEL.Value = node.LOG_LEVEL_FAILURE
// start a test node
node.Start(tangle.PLUGIN, PLUGIN)
bundleFactory := client.NewBundleFactory()
bundleFactory.AddInput(seed.GetAddress(0), -400)
bundleFactory.AddOutput(seed.GetAddress(1), 400, "Testmessage")
bundleFactory.AddOutput(client.NewAddress("SJKUKQP9SWUSUCPRQXCMDVDVZ9SHHESHIQNCXWBJF9UJSWE9ZYFHQWAUPCXC9S9DSHP9NDF9RLNPMZVCM"), 400, "Testmessage")
generatedBundle := bundleFactory.GenerateBundle(tipselection.GetRandomTip(), tipselection.GetRandomTip())
for _, transaction := range generatedBundle.GetTransactions() {
tangle.StoreTransaction(transaction)
}
var wg sync.WaitGroup
testResults := events.NewClosure(func(bundle *bundle.Bundle, transactions []*value_transaction.ValueTransaction) {
assert.Equal(t, bundle.GetHash(), generatedBundle.GetTransactions()[0].GetHash(), "invalid bundle hash")
assert.Equal(t, bundle.GetBundleEssenceHash(), generatedBundle.GetEssenceHash(), "invalid bundle essence hash")
assert.Equal(t, bundle.IsValueBundle(), true, "invalid value bundle status")
wg.Done()
})
Events.BundleSolid.Attach(testResults)
wg.Add(1)
if err := ProcessSolidBundleHead(generatedBundle.GetTransactions()[0]); err != nil {
t.Error(err)
}
wg.Wait()
Events.BundleSolid.Detach(testResults)
// shutdown test node
node.Shutdown()
}
......@@ -6,6 +6,7 @@ import (
"github.com/iotaledger/goshimmer/packages/model/bundle"
"github.com/iotaledger/goshimmer/packages/model/value_transaction"
"github.com/iotaledger/goshimmer/packages/workerpool"
"github.com/iotaledger/iota.go/signing"
"github.com/iotaledger/iota.go/trinary"
)
......@@ -13,23 +14,70 @@ var valueBundleProcessorWorkerPool = workerpool.New(func(task workerpool.Task) {
if err := ProcessSolidValueBundle(task.Param(0).(*bundle.Bundle), task.Param(1).([]*value_transaction.ValueTransaction)); err != nil {
Events.Error.Trigger(err)
}
task.Return(nil)
}, workerpool.WorkerCount(WORKER_COUNT), workerpool.QueueSize(2*WORKER_COUNT))
func ProcessSolidValueBundle(bundle *bundle.Bundle, bundleTransactions []*value_transaction.ValueTransaction) errors.IdentifiableError {
var concatenatedBundleEssences = make(trinary.Trits, len(bundleTransactions)*value_transaction.BUNDLE_ESSENCE_SIZE)
for i, bundleTransaction := range bundleTransactions {
copy(concatenatedBundleEssences[value_transaction.BUNDLE_ESSENCE_SIZE*i:value_transaction.BUNDLE_ESSENCE_SIZE*(i+1)], bundleTransaction.GetBundleEssence())
bundle.SetBundleEssenceHash(CalculateBundleHash(bundleTransactions))
Events.BundleSolid.Trigger(bundle, bundleTransactions)
return nil
}
func CalculateBundleHash(transactions []*value_transaction.ValueTransaction) trinary.Trytes {
var lastInputAddress trinary.Trytes
var concatenatedBundleEssences = make(trinary.Trits, len(transactions)*value_transaction.BUNDLE_ESSENCE_SIZE)
for i, bundleTransaction := range transactions {
if bundleTransaction.GetValue() <= 0 {
lastInputAddress = bundleTransaction.GetAddress()
}
copy(concatenatedBundleEssences[value_transaction.BUNDLE_ESSENCE_SIZE*i:value_transaction.BUNDLE_ESSENCE_SIZE*(i+1)], bundleTransaction.GetBundleEssence(lastInputAddress != bundleTransaction.GetAddress()))
}
var resp = make(trinary.Trits, 243)
var bundleHash = make(trinary.Trits, 243)
hasher := curl.NewCurl(243, 81)
hasher.Absorb(concatenatedBundleEssences, 0, len(concatenatedBundleEssences))
hasher.Squeeze(resp, 0, 243)
hasher.Squeeze(bundleHash, 0, 243)
bundle.SetBundleEssenceHash(trinary.MustTritsToTrytes(resp))
return trinary.MustTritsToTrytes(bundleHash)
}
Events.BundleSolid.Trigger(bundle, bundleTransactions)
func ValidateSignatures(bundleHash trinary.Hash, txs []*value_transaction.ValueTransaction) (bool, error) {
for i, tx := range txs {
// ignore all non-input transactions
if tx.GetValue() >= 0 {
continue
}
return nil
address := tx.GetAddress()
// it is unknown how many fragments there will be
fragments := []trinary.Trytes{tx.GetSignatureMessageFragment()}
// each consecutive meta transaction with the same address contains another signature fragment
for j := i; j < len(txs)-1; j++ {
otherTx := txs[j+1]
if otherTx.GetValue() != 0 || otherTx.GetAddress() != address {
break
}
fragments = append(fragments, otherTx.GetSignatureMessageFragment())
}
// validate all the fragments against the address using Kerl
valid, err := signing.ValidateSignatures(address, fragments, bundleHash, signing.NewKerl)
if err != nil {
return false, err
}
if !valid {
return false, nil
}
}
return true, nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment