Skip to content

Commit

Permalink
Use DN>DN signatures to validate DN>BVN anchors
Browse files Browse the repository at this point in the history
  • Loading branch information
firelizzard18 committed May 22, 2024
1 parent fd3fb50 commit 68522a4
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 18 deletions.
57 changes: 40 additions & 17 deletions internal/core/execute/v2/block/msg_block_anchor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
package block

import (
"strings"

"gitlab.com/accumulatenetwork/accumulate/internal/core"
"gitlab.com/accumulatenetwork/accumulate/internal/core/execute/v2/chain"
"gitlab.com/accumulatenetwork/accumulate/internal/database"
Expand All @@ -27,11 +29,9 @@ type BlockAnchor struct{}
type blockAnchorContext struct {
*TransactionContext

sequenced *messaging.SequencedMessage

sequenced *messaging.SequencedMessage
blockAnchor *messaging.BlockAnchor

signer protocol.Signer2
signer protocol.Signer2
}

func (x BlockAnchor) Validate(batch *database.Batch, ctx *MessageContext) (*protocol.TransactionStatus, error) {
Expand Down Expand Up @@ -136,18 +136,12 @@ func (x BlockAnchor) check(ctx *MessageContext, batch *database.Batch) (*blockAn

// Resolve placeholders
txn := txnMsg.Transaction
signed := seq.Hash()
if txn.Body.Type() == protocol.TransactionTypeRemote && ctx.GetActiveGlobals().ExecutorVersion.V2BaikonurEnabled() {
var err error
txn, err = ctx.getTransaction(batch, txn.ID().Hash())
if err != nil {
return nil, errors.UnknownError.WithFormat("load transaction: %w", err)
}

// Recalculate the hash with the full transaction
seq2 := seq.Copy()
seq2.Message = &messaging.TransactionMessage{Transaction: txn}
signed = seq2.Hash()
}

// Verify the transaction is an anchor
Expand All @@ -159,11 +153,6 @@ func (x BlockAnchor) check(ctx *MessageContext, batch *database.Batch) (*blockAn
return nil, errors.InternalError.WithFormat("sequence is missing source")
}

// Basic validation
if !anchor.Signature.Verify(nil, signed[:]) {
return nil, errors.Unauthenticated.WithFormat("invalid signature")
}

// Verify the signer is a validator of this partition
partition, ok := protocol.ParsePartitionUrl(seq.Source)
if !ok {
Expand All @@ -180,12 +169,46 @@ func (x BlockAnchor) check(ctx *MessageContext, batch *database.Batch) (*blockAn
return nil, errors.Unauthorized.WithFormat("key is not an active validator for %s", partition)
}

return &blockAnchorContext{
// Basic validation
ctx2 := &blockAnchorContext{
TransactionContext: ctx.txnWith(txn),
sequenced: seq,
blockAnchor: anchor,
signer: signer,
}, nil
}
err := x.checkSignature(ctx2)
if err != nil {
return nil, err
}

return ctx2, nil
}

func (x BlockAnchor) checkSignature(ctx *blockAnchorContext) error {
// Recalculate the hash in case the transaction was originally a remote
// transaction
txn := &messaging.TransactionMessage{Transaction: ctx.transaction}
seq := *ctx.sequenced
seq.Message = txn
if hash := seq.Hash(); ctx.blockAnchor.Signature.Verify(nil, hash[:]) {
return nil
}

// Allow reusing signatures from the DN
part, _ := protocol.ParsePartitionUrl(ctx.transaction.Header.Principal)
if ctx.GetActiveGlobals().ExecutorVersion.V2VandenbergEnabled() &&
ctx.transaction.Body.Type() == protocol.TransactionTypeDirectoryAnchor &&
!strings.EqualFold(part, protocol.Directory) {

seq.Destination = protocol.DnUrl()
txn.Transaction = txn.Transaction.Copy()
txn.Transaction.Header.Principal = protocol.DnUrl().JoinPath(ctx.transaction.Header.Principal.Path)
if hash := seq.Hash(); ctx.blockAnchor.Signature.Verify(nil, hash[:]) {
return nil
}
}

return errors.Unauthenticated.WithFormat("invalid signature")
}

func (x BlockAnchor) txnIsReady(batch *database.Batch, ctx *blockAnchorContext) (bool, error) {
Expand Down
101 changes: 101 additions & 0 deletions test/e2e/net_anchors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
package e2e

import (
"context"
"testing"

"github.com/stretchr/testify/require"
"gitlab.com/accumulatenetwork/accumulate/pkg/build"
"gitlab.com/accumulatenetwork/accumulate/pkg/types/messaging"
"gitlab.com/accumulatenetwork/accumulate/pkg/url"
. "gitlab.com/accumulatenetwork/accumulate/protocol"
. "gitlab.com/accumulatenetwork/accumulate/test/harness"
. "gitlab.com/accumulatenetwork/accumulate/test/helpers"
"gitlab.com/accumulatenetwork/accumulate/test/simulator"
acctesting "gitlab.com/accumulatenetwork/accumulate/test/testing"
)

// TestDropInitialAnchor is a simple test that simulates adverse network
Expand Down Expand Up @@ -55,3 +60,99 @@ func TestDropInitialAnchor(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 123, int(account.Balance.Int64()))
}

// TestReuseDirectoryAnchorSignatures verifies that anchor signatures the DN sends to
// itself can be reused for DN anchors sent to a BVN.
func TestReuseDirectoryAnchorSignatures(t *testing.T) {
const numVal, numNode = 3, 1

var trapBlock uint64
var dnSigs []KeySignature
var toBVN *messaging.SequencedMessage
anchorTrap := func(ctx context.Context, env *messaging.Envelope) (send bool, err error) {
if trapBlock == 0 || len(env.Messages) != 1 {
return true, nil
}

// Is the envelope a DN anchor signature for the target block?
blk, ok := env.Messages[0].(*messaging.BlockAnchor)
if !ok {
return true, nil
}
seq, ok := blk.Anchor.(*messaging.SequencedMessage)
if !ok {
return true, nil
}
txn, ok := seq.Message.(*messaging.TransactionMessage)
if !ok {
return true, nil
}
body, ok := txn.Transaction.Body.(*DirectoryAnchor)
if !ok || body.MinorBlockIndex != trapBlock {
return true, nil
}

// Capture signatures sent to the DN but don't drop them
if DnUrl().Equal(seq.Destination) {
dnSigs = append(dnSigs, blk.Signature)
return true, nil
}

// Let the first signature sent to the BVN through and drop the rest
if toBVN == nil {
toBVN = seq
return true, nil
}
return false, nil
}

// Initialize
sim := NewSim(t,
simulator.SimpleNetwork(t.Name(), numVal, numNode),
simulator.Genesis(GenesisTime),
simulator.CaptureDispatchedMessages(anchorTrap),
)

// Do something
alice := url.MustParse("alice")
aliceKey := acctesting.GenerateKey(alice)
MakeIdentity(t, sim.DatabaseFor(alice), alice, aliceKey[32:])
CreditCredits(t, sim.DatabaseFor(alice), alice.JoinPath("book", "1"), 1e9)

st := sim.BuildAndSubmitTxnSuccessfully(
build.Transaction().For(alice, "book", "1").BurnCredits(1).
SignWith(alice, "book", "1").Version(1).Timestamp(1).PrivateKey(aliceKey))

// Trap anchors
trapBlock = sim.S.BlockIndex(Directory)
sim.StepUntil(
True(func(*Harness) bool {
return toBVN != nil && len(dnSigs) == numVal*numNode
}))

// Verify the anchor sent to the BVN is pending
txn := toBVN.Message.(*messaging.TransactionMessage).Transaction
sim.StepUntil(
Txn(txn.ID()).IsPending())

// Take the anchors sent to the DN and send them to the BVN to resolve the
// anchor
for _, sig := range dnSigs {
sim.SubmitSuccessfully(&messaging.Envelope{
Messages: []messaging.Message{
&messaging.BlockAnchor{
Signature: sig,
Anchor: toBVN,
},
},
})
}

// Verify the anchor completes
sim.StepUntil(
Txn(txn.ID()).Completes())

// Verify the user transaction completes
sim.StepUntil(
Txn(st.TxID).Completes())
}
4 changes: 3 additions & 1 deletion test/simulator/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,9 @@ func (f *nodeFactory) makeCoreApp() *consensus.Node {
RunTask: execOpts.BackgroundTaskLauncher,
DropInitialAnchor: f.dropInitialAnchor,
EnableAnchorHealing: &enableAnchorHealing,
Intercept: f.interceptDispatchedMessages,

// Setting Intercept is not necessary because the dispatcher will
// intercept messages
}
err := conductor.Start(f.getEventBus())
if err != nil {
Expand Down

0 comments on commit 68522a4

Please sign in to comment.