Skip to content

Commit 27ae290

Browse files
committed
Implement accountability module (as a factory)
1 parent af7758d commit 27ae290

7 files changed

Lines changed: 590 additions & 0 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package simpleacc
2+
3+
import (
4+
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/common"
5+
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/certificates/fullcertificates"
6+
incommon "github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/common"
7+
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/poms"
8+
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/predecisions"
9+
"github.com/filecoin-project/mir/pkg/dsl"
10+
"github.com/filecoin-project/mir/pkg/factorymodule"
11+
"github.com/filecoin-project/mir/pkg/logging"
12+
"github.com/filecoin-project/mir/pkg/modules"
13+
accpbtypes "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/types"
14+
factorypbtypes "github.com/filecoin-project/mir/pkg/pb/factorypb/types"
15+
t "github.com/filecoin-project/mir/pkg/types"
16+
)
17+
18+
// ModuleConfig sets the module ids. All replicas are expected to use identical module configurations.
19+
type ModuleConfig = common.ModuleConfig
20+
21+
// ModuleParams sets the values for the parameters of an instance of the protocol.
22+
// All replicas are expected to use identical module parameters.
23+
type ModuleParams = common.ModuleParams
24+
25+
// NewModule creates a new instance of the (optinal) accountability
26+
// module.
27+
// This module can receive predecisions from an
28+
// ordering module (instead of the ordering module delivering them to the
29+
// application layer directly). It performs two all-to-all broadcasts
30+
// with signatures to ensure accountability. The first broadcast is a
31+
// signed predecision per participant. In the second broadcast, each
32+
// participant broadcasts a certificate containing a strong quorum of
33+
// signed predecisions that they each delivered from the first
34+
// broadcast. Termination occurs once a process receives a strong quorum
35+
// of correctly signed predecisions.
36+
// *Accountability* states that if an adversary (controlling less than a
37+
// strong quorum, but perhaps more or as much as a weak quorum) causes
38+
// a disagreement (two different correct processes delivering different
39+
// decisions) then there is at least a weak quorum of processes for which
40+
// all correct processes eventually receive Proofs-of-Misbehavior (PoMs).
41+
// In the case of this module, a PoM is a pair of signed predecisions for
42+
// different predecisions from the same node.
43+
// The module keeps looking for PoMs with newly received messages
44+
// (signed predecisions or certificates) after termination, until
45+
// it is garbage collected.
46+
//
47+
48+
// Intuition of correctness: a process only terminates if it receives a
49+
// strong quorum of signed predecisions from distinct processes, forming
50+
// a certificate. Once this process forms a certificate, it shares it
51+
// with the rest of participants. If all correct processes terminate,
52+
// then that means all correct processes will (i) deliver a strong quorum
53+
// of signed predecisions and (ii) broadcast them in a certificate. Thus,
54+
// if two correct processes p_1, p_2 disagree (decide d_1, d_2,
55+
// respectively) then that means they must have each delivered a strong
56+
// quorum of signed predecisions for different predecisions. By quorum
57+
// intersection, this means that at least a weak quorum of processes have
58+
// signed respective predecisions for d_1, d_2 and sent each of them to
59+
// the respective correct process p_1, p_2. Once p_1 receives the
60+
// certificate that p_2 broadcasted, p_1 will then generate a weak quorum
61+
// of PoMs (and vice versa) and broadcast it to the rest of processes.
62+
//
63+
// This module effectively implements a variant of the accountability
64+
// module of Civit et al. at https://ieeexplore.ieee.org/document/9820722/
65+
// Except that it ReportedPoMs map[t.NodeID]*accpbtypes.PoM
66+
// does not implement the optimization using threshold
67+
// signatures (as we have members with associated weight)
68+
//
69+
70+
// The optimistic variant of this module is a parametrizable optimization
71+
// in which certificates are optimistically believed to be correct. This way,
72+
// in the good case a correct process broadcasts a light certificate of O(1) bits
73+
// (instead of O(n) of a certificate)
74+
// and only actually sends the full certificate to nodes from which it receives a light certificate
75+
// for a predecision other than the locally Decided one. The recipient of the certificate can then
76+
// generate and broadcast the PoMs.
77+
//
78+
// ATTENTION: This module is intended to be used once per instance
79+
// (to avoid replay attacks) and reinstantiated in a factory.
80+
func NewModule(mc ModuleConfig, params *ModuleParams, logger logging.Logger) (modules.PassiveModule, error) {
81+
m := dsl.NewModule(mc.Self)
82+
83+
state := &incommon.State{
84+
SignedPredecisions: make(map[t.NodeID]*accpbtypes.SignedPredecision),
85+
PredecisionCount: make(map[string][]t.NodeID),
86+
SignedPredecision: nil,
87+
DecidedCertificate: nil,
88+
Predecided: false,
89+
UnsentPoMs: make([]*accpbtypes.PoM, 0),
90+
SentPoMs: make(map[t.NodeID]*accpbtypes.PoM),
91+
}
92+
93+
predecisions.IncludePredecisions(m, &mc, params, state, logger)
94+
fullcertificates.IncludeFullCertificate(m, &mc, params, state, logger)
95+
poms.IncludePoMs(m, &mc, params, state, logger)
96+
97+
return m, nil
98+
}
99+
100+
func NewReconfigurableModule(mc ModuleConfig, paramsTemplate ModuleParams, logger logging.Logger) modules.PassiveModule {
101+
if logger == nil {
102+
logger = logging.ConsoleErrorLogger
103+
}
104+
return factorymodule.New(
105+
mc.Self,
106+
factorymodule.DefaultParams(
107+
108+
// This function will be called whenever the factory module
109+
// is asked to create a new instance of the multisig collector.
110+
func(accID t.ModuleID, params *factorypbtypes.GeneratorParams) (modules.PassiveModule, error) {
111+
112+
// Extract the IDs of the nodes in the membership associated with this instance
113+
accParams := params.Type.(*factorypbtypes.GeneratorParams_AccModule).AccModule
114+
115+
// Create a copy of basic module config with an adapted ID for the submodule.
116+
submc := mc
117+
submc.Self = accID
118+
119+
// Fill in instance-specific parameters.
120+
moduleParams := paramsTemplate
121+
moduleParams.Membership = accParams.Membership
122+
123+
// Create a new instance of the multisig collector.
124+
accountabilityModule, err := NewModule(
125+
submc,
126+
&moduleParams,
127+
logger,
128+
)
129+
if err != nil {
130+
return nil, err
131+
}
132+
return accountabilityModule, nil
133+
},
134+
),
135+
logger,
136+
)
137+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package common
2+
3+
import (
4+
trantorpbtypes "github.com/filecoin-project/mir/pkg/pb/trantorpb/types"
5+
t "github.com/filecoin-project/mir/pkg/types"
6+
)
7+
8+
// ModuleConfig sets the module ids. All replicas are expected to use identical module configurations.
9+
type ModuleConfig struct {
10+
Self t.ModuleID // id of this module, used to uniquely identify an instance of the accountability module.
11+
// It prevents cross-instance signature replay attack and should be unique across all executions.
12+
13+
Ordering t.ModuleID // provides Predecisions
14+
App t.ModuleID // receives Decisions and/or PoMs
15+
Crypto t.ModuleID // provides cryptographic primitives
16+
Net t.ModuleID // provides network primitives
17+
}
18+
19+
// ModuleParams sets the values for the parameters of an instance of the protocol.
20+
// All replicas are expected to use identical module parameters.
21+
type ModuleParams struct {
22+
Membership *trantorpbtypes.Membership // the list of participating nodes
23+
LightCertificates bool
24+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package fullcertificates
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/common"
7+
incommon "github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/common"
8+
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/poms"
9+
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/predecisions"
10+
"github.com/filecoin-project/mir/pkg/dsl"
11+
"github.com/filecoin-project/mir/pkg/logging"
12+
accpbdsl "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/dsl"
13+
accpbtypes "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/types"
14+
cryptopbdsl "github.com/filecoin-project/mir/pkg/pb/cryptopb/dsl"
15+
cryptopbtypes "github.com/filecoin-project/mir/pkg/pb/cryptopb/types"
16+
t "github.com/filecoin-project/mir/pkg/types"
17+
"github.com/filecoin-project/mir/pkg/util/maputil"
18+
"github.com/filecoin-project/mir/pkg/util/membutil"
19+
"github.com/filecoin-project/mir/pkg/util/sliceutil"
20+
)
21+
22+
// IncludeFullCertificate implements the full certificate brodcast and verification
23+
// in order to find PoMs
24+
func IncludeFullCertificate(m dsl.Module,
25+
mc *common.ModuleConfig,
26+
params *common.ModuleParams,
27+
state *incommon.State,
28+
logger logging.Logger,
29+
) {
30+
31+
accpbdsl.UponFullCertificateReceived(m, func(from t.NodeID, certificate map[t.NodeID]*accpbtypes.SignedPredecision) error {
32+
predecision, empty := maputil.AnyVal(certificate)
33+
if empty {
34+
logger.Log(logging.LevelDebug, "Received empty predecision certificate")
35+
return nil
36+
}
37+
38+
if !membutil.HaveStrongQuorum(params.Membership, maputil.GetKeys(certificate)) {
39+
logger.Log(logging.LevelDebug, "Received predecision certificate without strong quorum")
40+
return nil
41+
}
42+
43+
for _, v := range certificate {
44+
if !reflect.DeepEqual(predecision.Predecision, v.Predecision) {
45+
logger.Log(logging.LevelDebug, "Received predecision certificate with different predecisions")
46+
return nil
47+
}
48+
}
49+
50+
// Verify all signatures in certificate
51+
cryptopbdsl.VerifySigs(
52+
m,
53+
mc.Crypto,
54+
sliceutil.Transform(maputil.GetValues(certificate),
55+
func(i int, sp *accpbtypes.SignedPredecision) *cryptopbtypes.SignedData {
56+
return &cryptopbtypes.SignedData{[][]byte{sp.Predecision, []byte(mc.Self)}}
57+
}),
58+
sliceutil.Transform(maputil.GetValues(certificate),
59+
func(i int, sp *accpbtypes.SignedPredecision) []byte {
60+
return sp.Signature
61+
}),
62+
maputil.GetKeys(certificate),
63+
&verifySigs{
64+
certificate: certificate,
65+
},
66+
)
67+
return nil
68+
})
69+
70+
cryptopbdsl.UponSigsVerified(m, func(nodeIds []t.NodeID, errs []error, allOk bool, vsr *verifySigs) error {
71+
for i, nodeId := range nodeIds {
72+
predecisions.ApplySigVerified(m, mc, params, state, nodeId, errs[i], vsr.certificate[nodeId], false, logger)
73+
}
74+
poms.SendPoMs(m, mc, params, state, logger)
75+
return nil
76+
})
77+
}
78+
79+
type verifySigs struct {
80+
certificate map[t.NodeID]*accpbtypes.SignedPredecision
81+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package lightcertificates
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/filecoin-project/mir/pkg/accountability/simpleacc/common"
7+
incommon "github.com/filecoin-project/mir/pkg/accountability/simpleacc/internal/common"
8+
"github.com/filecoin-project/mir/pkg/dsl"
9+
"github.com/filecoin-project/mir/pkg/logging"
10+
accpbdsl "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/dsl"
11+
accpbmsgs "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/msgs"
12+
transportpbdsl "github.com/filecoin-project/mir/pkg/pb/transportpb/dsl"
13+
t "github.com/filecoin-project/mir/pkg/types"
14+
"github.com/filecoin-project/mir/pkg/util/maputil"
15+
)
16+
17+
// IncludeLightCertificate implements the (optional) light certificate optimization
18+
// that optimistically sends only the predecision during the light certificate
19+
// so that in the good case where there are no disagreements and all processes
20+
// are correct there is no need to broadcast a full certificate containing O(n) signatures
21+
func IncludeLightCertificate(m dsl.Module,
22+
mc *common.ModuleConfig,
23+
params *common.ModuleParams,
24+
state *incommon.State,
25+
logger logging.Logger,
26+
) {
27+
28+
lightCertificates := make(map[t.NodeID][]byte)
29+
30+
accpbdsl.UponLightCertificateReceived(m, func(from t.NodeID, data []byte) error {
31+
32+
if state.DecidedCertificate == nil {
33+
logger.Log(logging.LevelDebug, "Received light certificate before decided certificate, buffering it")
34+
lightCertificates[from] = data
35+
return nil
36+
}
37+
38+
decision, _ := maputil.AnyVal(state.DecidedCertificate)
39+
40+
if !reflect.DeepEqual(decision.Predecision, data) {
41+
logger.Log(logging.LevelWarn, "Received light certificate with different predecision than local decision! sending full certificate to node %v", from)
42+
transportpbdsl.SendMessage(
43+
m,
44+
mc.Net,
45+
accpbmsgs.FullCertificate(mc.Self,
46+
state.DecidedCertificate),
47+
[]t.NodeID{from})
48+
}
49+
return nil
50+
})
51+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package common
2+
3+
import (
4+
accpbtypes "github.com/filecoin-project/mir/pkg/pb/accountabilitypb/types"
5+
t "github.com/filecoin-project/mir/pkg/types"
6+
)
7+
8+
type State struct {
9+
SignedPredecisions map[t.NodeID]*accpbtypes.SignedPredecision
10+
PredecisionCount map[string][]t.NodeID
11+
SignedPredecision *accpbtypes.SignedPredecision
12+
DecidedCertificate map[t.NodeID]*accpbtypes.SignedPredecision
13+
Predecided bool
14+
UnsentPoMs []*accpbtypes.PoM
15+
SentPoMs map[t.NodeID]*accpbtypes.PoM
16+
}

0 commit comments

Comments
 (0)