Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Accountability module #505

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions pkg/accountability/simpleacc/accountability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package simpleacc

import (
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/common"
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/certificates/fullcertificates"
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/certificates/lightcertificates"
incommon "github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/common"
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/poms"
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/predecisions"
"github.com/filecoin-project/mir/pkg/dsl"
"github.com/filecoin-project/mir/pkg/factorymodule"
"github.com/filecoin-project/mir/pkg/logging"
"github.com/filecoin-project/mir/pkg/modules"
accpbtypes "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/types"
factorypbtypes "github.com/filecoin-project/mir/pkg/pb/factorypb/types"
t "github.com/filecoin-project/mir/pkg/types"
)

// ModuleConfig sets the module ids. All replicas are expected to use identical module configurations.
type ModuleConfig = common.ModuleConfig

// ModuleParams sets the values for the parameters of an instance of the protocol.
// All replicas are expected to use identical module parameters.
type ModuleParams = common.ModuleParams

// NewModule creates a new instance of the (optinal) accountability
// module.
// This module can receive decisions from a module that ensures agreement
// (for example, receive a decision from the ordering module, instead of
// the ordering module delivering them to the application layer directly),
// and treats them as predecisions. It performs two all-to-all broadcasts
// with signatures to ensure accountability. The first broadcast is a
// signed predecision per participant. In the second broadcast, each
// participant broadcasts a certificate containing a strong quorum of
// signed predecisions that they each delivered from the first
// broadcast. Termination occurs once a process receives a strong quorum
// of correctly signed predecisions.
// *Accountability* states that if an adversary (controlling less than a
// strong quorum, but perhaps more or as much as a weak quorum) causes
// a disagreement (two different correct processes delivering different
// decisions) then all correct processes eventually receive Proofs-of-Misbehavior (PoMs)
// for a provably malicious coalition at least the size of a weak quorum.
// In the case of this module, a PoM is a pair of different predecisions signed
// by the same node.
// The module keeps looking for PoMs with newly received messages
// (signed predecisions or certificates) after termination, until
// it is garbage collected.
//

// Intuition of correctness: a process only terminates if it receives a
// strong quorum of signed predecisions from distinct processes, forming
// a certificate. Once this process forms a certificate, it shares it
// with the rest of participants. If all correct processes terminate,
// then that means all correct processes will (i) deliver a strong quorum
// of signed predecisions and (ii) broadcast them in a certificate. Thus,
// if two correct processes p_1, p_2 disagree (decide d_1, d_2,
// respectively) then that means they must have each delivered a strong
// quorum of signed predecisions for different predecisions. By quorum
// intersection, this means that at least a weak quorum of processes have
// signed respective predecisions for d_1, d_2 and sent each of them to
// the respective correct process p_1, p_2. Once p_1 receives the
// certificate that p_2 broadcasted, p_1 will then generate a weak quorum
// of PoMs (and vice versa) and broadcast it to the rest of processes.
//
// This module effectively implements a variant of the accountability
// module of Civit et al. at https://ieeexplore.ieee.org/document/9820722/
// Except that it does not implement the optimization using threshold
// signatures (as we have members with associated weight)
//

// The optimistic variant of this module is a parametrizable optimization
// in which certificates are optimistically believed to be correct. This way,
// in the good case a correct process broadcasts a light certificate of O(1) bits
// (instead of O(n) of a certificate)
// and only actually sends the full certificate to nodes from which it receives a light certificate
// for a predecision other than the locally Decided one. The recipient of the certificate can then
// generate and broadcast the PoMs.
//
// ATTENTION: This module is intended to be used once per instance
// (to avoid replay attacks) and reinstantiated in a factory.
func NewModule(
mc ModuleConfig,
params *ModuleParams,
logger logging.Logger) (modules.PassiveModule, error) {
m := dsl.NewModule(mc.Self)

state := &incommon.State{
SignedPredecisions: make(map[t.NodeID]*accpbtypes.SignedPredecision),
PredecisionNodeIDs: make(map[string][]t.NodeID),
LocalPredecision: nil,
DecidedCertificate: nil,
Predecided: false,
UnhandledPoMs: make([]*accpbtypes.PoM, 0),
HandledPoMs: make(map[t.NodeID]*accpbtypes.PoM),
}

predecisions.IncludePredecisions(m, &mc, params, state, logger)
fullcertificates.IncludeFullCertificate(m, &mc, params, state, logger)
if params.LightCertificates {
lightcertificates.IncludeLightCertificate(m, &mc, params, state, logger)
}
poms.IncludePoMs(m, &mc, params, state, logger)

return m, nil
}

func NewReconfigurableModule(mc ModuleConfig, paramsTemplate ModuleParams, logger logging.Logger) modules.PassiveModule {
if logger == nil {
logger = logging.ConsoleErrorLogger
}
return factorymodule.New(
mc.Self,
factorymodule.DefaultParams(

// This function will be called whenever the factory module
// is asked to create a new instance of the accountabuility module.
func(accID t.ModuleID, params *factorypbtypes.GeneratorParams) (modules.PassiveModule, error) {

accParams := params.Type.(*factorypbtypes.GeneratorParams_AccModule).AccModule

// Create a copy of basic module config with an adapted ID for the submodule.
submc := mc
submc.Self = accID

// Fill in instance-specific parameters.
moduleParams := paramsTemplate
moduleParams.Membership = accParams.Membership
moduleParams.RetentionIndex = accParams.RetentionIndex

// Create a new instance of the multisig collector.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy-paste artifact?

accountabilityModule, err := NewModule(
submc,
&moduleParams,
logger,
)
if err != nil {
return nil, err
}
return accountabilityModule, nil
},
),
logger,
)
}
39 changes: 39 additions & 0 deletions pkg/accountability/simpleacc/common/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package common

import (
incommon "github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/common"
"github.com/filecoin-project/mir/pkg/dsl"
"github.com/filecoin-project/mir/pkg/logging"
accpbtypes "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/types"
trantorpbtypes "github.com/filecoin-project/mir/pkg/pb/trantorpb/types"
timertypes "github.com/filecoin-project/mir/pkg/timer/types"
tt "github.com/filecoin-project/mir/pkg/trantor/types"
t "github.com/filecoin-project/mir/pkg/types"
)

// ModuleConfig sets the module ids. All replicas are expected to use identical module configurations.
type ModuleConfig struct {
Self t.ModuleID // id of this module, used to uniquely identify an instance of the accountability module.
// It prevents cross-instance signature replay attack and should be unique across all executions.

Ordering t.ModuleID // provides Predecisions
App t.ModuleID // receives Decisions and/or PoMs
Crypto t.ModuleID // provides cryptographic primitives
Timer t.ModuleID // provides Timing primitives
Net t.ModuleID // provides network primitives
}

// ModuleParams sets the values for the parameters of an instance of the protocol.
// All replicas are expected to use identical module parameters.
type ModuleParams struct {
Membership *trantorpbtypes.Membership // The list of participating nodes.
LightCertificates bool
ResendFrequency timertypes.Duration // Frequency with which messages in the critical path are re-sent
RetentionIndex tt.RetentionIndex
PoMsHandler func(m dsl.Module, // Function to be called when PoMs detected.
mc *ModuleConfig,
params *ModuleParams,
state *incommon.State,
poms []*accpbtypes.PoM,
logger logging.Logger)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package fullcertificates

import (
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/common"
incommon "github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/common"
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/poms"
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/predecisions"
"github.com/filecoin-project/mir/pkg/dsl"
"github.com/filecoin-project/mir/pkg/logging"
accpbdsl "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/dsl"
accpbtypes "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/types"
cryptopbdsl "github.com/filecoin-project/mir/pkg/pb/cryptopb/dsl"
cryptopbtypes "github.com/filecoin-project/mir/pkg/pb/cryptopb/types"
t "github.com/filecoin-project/mir/pkg/types"
"github.com/filecoin-project/mir/pkg/util/maputil"
"github.com/filecoin-project/mir/pkg/util/membutil"
"github.com/filecoin-project/mir/pkg/util/sliceutil"
)

// IncludeFullCertificate implements the full certificate brodcast and verification
// in order to find PoMs.
func IncludeFullCertificate(m dsl.Module,
mc *common.ModuleConfig,
params *common.ModuleParams,
state *incommon.State,
logger logging.Logger,
) {

accpbdsl.UponFullCertificateReceived(m, func(from t.NodeID, decision []byte, certificate map[t.NodeID][]byte) error {
if len(certificate) == 0 {
logger.Log(logging.LevelDebug, "Ignoring empty predecision certificate")
return nil
}

if !membutil.HaveStrongQuorum(params.Membership, maputil.GetKeys(certificate)) {
logger.Log(logging.LevelDebug, "Ignoring predecision certificate without strong quorum")
return nil
}

// Verify all signatures in certificate.
cryptopbdsl.VerifySigs(
m,
mc.Crypto,
sliceutil.Generate(
len(certificate),
func(i int) *cryptopbtypes.SignedData {
return &cryptopbtypes.SignedData{
Data: [][]byte{decision},
}
}),
maputil.GetValues(certificate),
maputil.GetKeys(certificate),
&verifySigs{
certificate: &accpbtypes.FullCertificate{
Decision: decision,
Signatures: certificate,
},
},
)
return nil
})

cryptopbdsl.UponSigsVerified(m, func(nodeIds []t.NodeID, errs []error, allOk bool, vsr *verifySigs) error {
for i, nodeID := range nodeIds {
sp := &accpbtypes.SignedPredecision{
Predecision: vsr.certificate.Decision,
Signature: vsr.certificate.Signatures[nodeID],
}
predecisions.ApplySigVerified(m, mc, params, state, nodeID, errs[i], sp, false, logger)
}
poms.HandlePoMs(m, mc, params, state, logger)
return nil
})
}

type verifySigs struct {
certificate *accpbtypes.FullCertificate
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package lightcertificates

import (
"reflect"

"github.com/filecoin-project/mir/pkg/accountability/simpleacc/common"
incommon "github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/common"
"github.com/filecoin-project/mir/pkg/dsl"
"github.com/filecoin-project/mir/pkg/logging"
accpbdsl "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/dsl"
accpbmsgs "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/msgs"
transportpbdsl "github.com/filecoin-project/mir/pkg/pb/transportpb/dsl"
t "github.com/filecoin-project/mir/pkg/types"
)

// IncludeLightCertificate implements the (optional) light certificate optimization
// that optimistically sends only the predecision during the light certificate
// so that in the good case where there are no disagreements and all processes
// are correct there is no need to broadcast a full certificate containing O(n) signatures.
func IncludeLightCertificate(m dsl.Module,
mc *common.ModuleConfig,
params *common.ModuleParams,
state *incommon.State,
logger logging.Logger,
) {

accpbdsl.UponLightCertificateReceived(m, func(from t.NodeID, data []byte) error {

if !params.LightCertificates {
return nil
}

if state.DecidedCertificate == nil {
logger.Log(logging.LevelDebug, "Received light certificate before decided certificate, buffering it")
state.LightCertificates[from] = data
return nil
}

applyLightCertificateReceived(m, mc, state, from, data, logger)
return nil
})
}

func applyLightCertificateReceived(
m dsl.Module,
mc *common.ModuleConfig,
state *incommon.State,
from t.NodeID,
data []byte,
logger logging.Logger) {

decision := state.DecidedCertificate.Decision

if !reflect.DeepEqual(decision, data) {
logger.Log(logging.LevelWarn, "Received light certificate with different predecision than local decision! sending full certificate to node %v", from)
transportpbdsl.SendMessage(
m,
mc.Net,
accpbmsgs.FullCertificate(mc.Self,
state.DecidedCertificate.Decision,
state.DecidedCertificate.Signatures),
[]t.NodeID{from})
}

}

func ApplyLightCertificatesBuffered(
m dsl.Module,
mc *common.ModuleConfig,
state *incommon.State,
logger logging.Logger) {

for from, data := range state.LightCertificates {
applyLightCertificateReceived(m, mc, state, from, data, logger)
}

}
24 changes: 24 additions & 0 deletions pkg/accountability/simpleacc/internal/common/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package common

import (
accpbtypes "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/types"
isspbtypes "github.com/filecoin-project/mir/pkg/pb/isspb/types"
t "github.com/filecoin-project/mir/pkg/types"
)

type State struct {
SignedPredecisions map[t.NodeID]*accpbtypes.SignedPredecision // Map of received signed predicisions (including own's) with their signer as key.
PredecisionNodeIDs map[string][]t.NodeID // Map of predecisions and the nodes that have signed them with the predecision as key,
// used for fast verification of whether a predecision is predecided by a strong quorum.
LocalPredecision *LocalPredecision // Decision locally decided
DecidedCertificate *accpbtypes.FullCertificate // Locally decided certificate (predecision and list of signatures with signers as key)
Predecided bool // Whether this process has received a predecided value from calling module.
UnhandledPoMs []*accpbtypes.PoM // List of PoMs not yet sent to the application.
HandledPoMs map[t.NodeID]*accpbtypes.PoM // List of PoMs already sent to the application with the signer as key.
LightCertificates map[t.NodeID][]byte // Map of light certificates with the signer as key, buffered if no local decision made yet.
}

type LocalPredecision struct {
SBDeliver *isspbtypes.SBDeliver // Actual payload of the local predecision.
SignedPredecision *accpbtypes.SignedPredecision // Own signed predecision.
}
Loading